Skip to content

Commit

Permalink
chore(react-chart): prevent scroll event on zooming (#1984)
Browse files Browse the repository at this point in the history
  • Loading branch information
Krijovnick authored Apr 23, 2019
1 parent 43c967c commit dd395e3
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 61 deletions.
10 changes: 1 addition & 9 deletions packages/dx-chart-core/src/types/utils.event-tracker.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,7 @@ export type HandlersObject = {
readonly pointerMoveHandlers: HandlerFnList;
};
/** @internal */
export type EventHandlers = {
click?: EventHandlerFn;
pointermove?: EventHandlerFn;
pointerleave?: EventHandlerFn;
touchmove?: EventHandlerFn;
touchleave?: EventHandlerFn;
mousemove?: EventHandlerFn;
mouseleave?: EventHandlerFn;
};
export type EventHandlers = { [key: string]: EventHandlerFn };
/** @internal */
export type EventHandlerFn = (e: any) => void;
/** The click event data */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ export default class Demo extends React.PureComponent {
)}
label={label}
/>
// <p style={{ margin: '20px' }}>
// <label htmlFor={id}>
// <input type="checkbox" id={id} checked={checked} onChange={this.submit} />
// {label}
// </label>
// </p>
);
}

Expand Down
30 changes: 30 additions & 0 deletions packages/dx-react-chart/src/plugins/zoom-pan.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ jest.mock('@devexpress/dx-chart-core', () => ({
const DragBoxComponent = () => null;

describe('ZoomAndPan', () => {
const addEventListener = jest.fn();
const removeEventListener = jest.fn();
const defaultDeps = {
getter: {
domains: 'test-domains',
ranges: 'test-ranges',
rootRef: {
current: {
addEventListener,
removeEventListener,
},
},
},
};
const defaultProps = {
Expand Down Expand Up @@ -61,4 +69,26 @@ describe('ZoomAndPan', () => {
ranges: 'adjusted-ranges',
});
});

it('should attach events', () => {
const tree = mount((
<PluginHost>
{pluginDepsToComponents(defaultDeps)}
<ZoomAndPan {...defaultProps} />
</PluginHost>
));
expect(addEventListener).toBeCalledTimes(5);
});

it('should detach events', () => {
const tree = mount((
<PluginHost>
{pluginDepsToComponents(defaultDeps)}
<ZoomAndPan {...defaultProps} />
</PluginHost>
));

tree.unmount();
expect(removeEventListener).toBeCalledTimes(5);
});
});
126 changes: 81 additions & 45 deletions packages/dx-react-chart/src/plugins/zoom-pan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,53 @@ import {
ScalesCache,
} from '@devexpress/dx-chart-core';
import {
ZoomAndPanProps, ZoomAndPanState, NumberArray,
ZoomAndPanProps, ZoomAndPanState, NumberArray, ZoomPanProviderProps, EventHandlers,
} from '../types';

const events = {
wheel: 'onWheel',
mousedown: 'onDown',
touchstart: 'onDown',
touchmove: 'onTouchMove',
touchend: 'onTouchEnd',
};

class ZoomPanProvider extends React.PureComponent<ZoomPanProviderProps> {
ref!: Element;
handlers!: EventHandlers;
componentDidMount() {
this.ref = this.props.rootRef.current!;
this.handlers = Object.keys(events).reduce((prev, key) => {
return {
...prev,
[key]: (e: any) => { this.props[events[key]](e); },
};
}, {});
this.attachEvents();
}

attachEvents() {
Object.keys(this.handlers).forEach((el) => {
this.ref.addEventListener(el, this.handlers[el], { passive: false });
});
}

detachEvents() {
Object.keys(this.handlers).forEach((el) => {
this.ref.removeEventListener(el, this.handlers[el]);
});
}

componentWillUnmount() {
this.detachEvents();
}

render() {
return null;
}
}

// tslint:disable-next-line:max-classes-per-file
class ZoomAndPanBase extends React.PureComponent<ZoomAndPanProps, ZoomAndPanState> {
static components: PluginComponents = {
dragBoxComponent: 'DragBox',
Expand Down Expand Up @@ -51,37 +95,35 @@ class ZoomAndPanBase extends React.PureComponent<ZoomAndPanProps, ZoomAndPanStat
};
}

handleStart(zoomRegionKey: string) {
return (e: any) => {
this.offset = getOffset(e.currentTarget);
handleStart(zoomRegionKey: string, e: any) {
this.offset = getOffset(e.currentTarget);
// Rectangle mode should be canceled if "zoomRegionKey" is released during mouse movevent or
// not pressed when mouse is up. To do it access to "event" object is required in
// "handleMouseMove" and "handleMouseUp".
// TODO: Provide rectangle mode canceling.
if (isKeyPressed(e.nativeEvent, zoomRegionKey)) {
this.rectOrigin = [e.pageX - this.offset[0], e.pageY - this.offset[1]];
}
if (e.touches && e.touches.length === 2) {
this.multiTouchDelta = getDeltaForTouches(e.touches).delta;
}
};
if (isKeyPressed(e, zoomRegionKey)) {
this.rectOrigin = [e.pageX - this.offset[0], e.pageY - this.offset[1]];
}
if (e.touches && e.touches.length === 2) {
this.multiTouchDelta = getDeltaForTouches(e.touches).delta;
}
}

handleTouchMove(scales: ScalesCache) {
return (e: any) => {
if (e.touches && e.touches.length === 2) {
const current = getDeltaForTouches(e.touches);
this.zoom(scales, current.delta - this.multiTouchDelta!, current.center);
this.multiTouchDelta = current.delta;
}
};
handleTouchMove(scales: ScalesCache, e: any) {
e.preventDefault();
if (e.touches && e.touches.length === 2) {
const current = getDeltaForTouches(e.touches);
this.zoom(scales, current.delta - this.multiTouchDelta!, current.center);
this.multiTouchDelta = current.delta;
} else {
this.handleMouseMove(scales, { x: e.touches[0].clientX, y: e.touches[0].clientY });
}
}

handleMouseMove(scales: ScalesCache, clientOffset: { x: number, y: number }) {
if (this.multiTouchDelta) {
return;
}

const coords: NumberArray = [clientOffset.x - this.offset[0], clientOffset.y - this.offset[1]];
if (!this.lastCoordinates) {
this.lastCoordinates = coords;
Expand Down Expand Up @@ -150,12 +192,11 @@ class ZoomAndPanBase extends React.PureComponent<ZoomAndPanProps, ZoomAndPanStat
});
}

handleScroll(scales: ScalesCache) {
return (e: any) => {
const offset = getOffset(e.currentTarget);
const center: NumberArray = [e.pageX - offset[0], e.pageY - offset[1]];
this.zoom(scales, e.nativeEvent.wheelDelta, center);
};
handleScroll(scales: ScalesCache, e: any) {
e.preventDefault();
const offset = getOffset(e.currentTarget);
const center: NumberArray = [e.pageX - offset[0], e.pageY - offset[1]];
this.zoom(scales, e.wheelDelta, center);
}

render() {
Expand All @@ -173,31 +214,26 @@ class ZoomAndPanBase extends React.PureComponent<ZoomAndPanProps, ZoomAndPanStat
<Getter name="ranges" computed={getAdjustedLayout} />
<Template name="root">
<TemplateConnector>
{({ scales }) =>
{({ scales, rootRef }) => (
<React.Fragment>
<DragDropProvider>
<DropTarget
onOver={({ _, clientOffset }) => this.handleMouseMove(scales, clientOffset)}
onDrop={() => this.handleMouseUp(scales)}
>
<DragSource payload={null}>
<TemplatePlaceholder />
</DragSource>
<DragSource payload={null}>
<TemplatePlaceholder/>
</DragSource>
</DropTarget>
</DragDropProvider>}
</TemplateConnector>
</Template>

<Template name="canvas">
<TemplateConnector>
{({ scales }) =>
<TemplatePlaceholder
params={{
onWheel: this.handleScroll(scales),
onMouseDown: this.handleStart(zoomRegionKey!),
onTouchStart: this.handleStart('none'),
onTouchMove: this.handleTouchMove(scales),
}}
/>}
</DragDropProvider>
<ZoomPanProvider
rootRef={rootRef}
onWheel={e => this.handleScroll(scales, e)}
onDown={e => this.handleStart(zoomRegionKey!, e)}
onTouchMove={e => this.handleTouchMove(scales, e)}
onTouchEnd={e => this.handleMouseUp(scales)}
/>
</React.Fragment>)}
</TemplateConnector>
</Template>

Expand Down
2 changes: 1 addition & 1 deletion packages/dx-react-chart/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
BBoxes, BBoxesChange,
StackList, StacksOptions, OffsetFn, OrderFn,
EventHandlers, NumberArray,
DomainBounds, ViewportOptions, OnViewportChangeFn,
DomainBounds, ViewportOptions, OnViewportChangeFn, EventHandlerFn,

AreaSeries, LineSeries, SplineSeries, BarSeries, ScatterSeries, PieSeries,
SeriesProps, PathComponentProps, PathComponentPathProps, PointComponentProps,
Expand Down
10 changes: 10 additions & 0 deletions packages/dx-react-chart/src/types/plugins.zoom-pan.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ViewportOptions,
OnViewportChangeFn,
EventHandlerFn,
} from './index';

export interface ZoomAndPanProps {
Expand Down Expand Up @@ -35,3 +36,12 @@ export namespace ZoomAndPan {
rect: Rect;
}
}

/** @internal */
export type ZoomPanProviderProps = {
rootRef: React.RefObject<Element>;
onWheel: EventHandlerFn;
onDown: EventHandlerFn,
onTouchMove: EventHandlerFn,
onTouchEnd: EventHandlerFn
};

0 comments on commit dd395e3

Please sign in to comment.