[FEATURE] Respect event.defaultPrevented
Closed this issue · 8 comments
I would like to prevent panning while the mouse is inside this red area to allow some drag and drop features:
To accomplish this, I added some Listeners for all events react-pan-and-zoom-hoc
registers. Inside of this listeners, I try to call event.preventDefault();
:
<div
className={styles.selectedProductImageRenderer}
style={wrapperStyle}
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
return false;
}}
onMouseUp={(event) => {
event.preventDefault();
event.stopPropagation();
return false;
}}
onMouseMove={(event) => {
event.preventDefault();
event.stopPropagation();
return false;
}}
onTouchStart={(event) => {
event.preventDefault();
event.stopPropagation();
return false;
}}
>
foo
</div>
This seems to work as far as defaultPrevented
is set to true
when I use my event listeners (Screenshot is the output of console.log('handleMouseMove', event);
):
Then I thought it would be an easy task by just adding something like event.defaultPrevented
in handleMouseDown
, handleMouseMove
and handleMouseUp
like this:
handleMouseDown = (event: MouseEvent | TouchEvent) => {
- if (!this.panning && !this.boxZoom) {
+ if (!this.panning && !this.boxZoom && !event.defaultPrevented) {
handleMouseMove = (event: MouseEvent | TouchEvent) => {
- if (this.panning || this.boxZoom) {
+ if ((this.panning || this.boxZoom) && !event.defaultPrevented) {
handleMouseUp = (event: MouseEvent | TouchEvent) => {
- if (this.panning || this.boxZoom) {
+ if ((this.panning || this.boxZoom) && !event.defaultPrevented) {
Am I missing something? Would be nice to get an basic support of event.defaultPrevented
. I think event.defaultPrevented
is kinda private so I cannot access it.
I would also love to fix this for you if you have no time. Can you confirm my POC in the issue description? It kinda works, but not "stable" and I am not sure if I followed the right/best-practive approach to fix this issue. :-)
Hi @blaues0cke
Sorry for the delay, I was on holiday and am still catching up to stuff.
A PR will be very welcome, I think your POC is spot on.
Thanks :)
@woutervh- I finally fixed the issue/found a workaround. The problem is, that component.addEventListener('mousedown', this.handleMouseDown);
and component.addEventListener('touchstart', this.handleMouseDown);
in componentDidMount
in panAndZoomHoc.tsx
is called so early that my own onMouseDown
and onTouchStart
are registered too late. So your handleMouseDown
is called even before event.preventDefault()
was called.
My workaround now is to register a own mousedown
in the callback of reference
. This seems to be called earlier and allows me to call event.preventDefault()
early enough. This also makes the explicit check of event.defaultPrevented
obsolete since this is handled by the event queue. So basically there is no need to fix this in react-pan-and-zoom-hoc
but it may be sill cool to have an easier way to achieve this? May it be a workaround to wait one render-iteration before registering your own events?
This is my examples/main.js
with the POC:
import React from 'react';
import ReactDOM from 'react-dom';
import panAndZoomHoc from '../lib/panAndZoomHoc';
const InteractiveDiv = panAndZoomHoc('div');
class App extends React.Component {
state = {
x: 0.5,
y: 0.5,
scale: 1
};
+ reftest = null;
+
+ handleMouseDown = (event) => {
+ event.stopPropagation();
+ };
+
handlePanAndZoom = (x, y, scale) => {
this.setState({ x, y, scale });
}
handlePanMove = (x, y) => {
this.setState({ x, y });
}
handleZoomEnd = () => console.log('Zoom has ended.');
+ setViewReference = (reference) => {
+ this.reftest = reference;
+
+ reference.addEventListener('mousedown', this.handleMouseDown, false);
+ };
+
transformPoint({ x, y }) {
return {
x: 0.5 + this.state.scale * (x - this.state.x),
y: 0.5 + this.state.scale * (y - this.state.y)
};
}
render() {
const { x, y, scale } = this.state;
const p1 = this.transformPoint({x: 0.5, y: 0.5});
return <InteractiveDiv
x={x}
y={y}
scale={scale}
scaleFactor={Math.sqrt(2)}
minScale={0.5}
maxScale={2}
onPanAndZoom={this.handlePanAndZoom}
ignorePanOutside
style={{ width: 500, height: 500, boxSizing: 'border-box', border: '1px solid black', position: 'relative' }}
onPanMove={this.handlePanMove}
onZoomEnd={this.handleZoomEnd}
>
{/* Viewport */}
<div style={{ position: 'absolute', width: 500, height: 500, boxSizing: 'border-box', border: '1px dashed blue', transform: `translate(${(x - 0.5) * 500}px, ${(y - 0.5) * 500}px) scale(${1 / scale})` }} />
{/* Objects - original position and zoom */}
<div style={{ position: 'absolute', width: 50, height: 50, backgroundColor: 'lightgrey', transform: `translate(250px, 250px) translate(-25px, -25px)` }} />
<div style={{ position: 'absolute', width: 50, height: 50, backgroundColor: 'lightgrey', transform: `translate(250px, 250px) translate(25px, 25px)` }} />
{/* Objects */}
<div style={{ position: 'absolute', width: 50 * this.state.scale, height: 50 * this.state.scale, backgroundColor: 'black', transform: `translate(${p1.x * 500}px, ${p1.y * 500}px) translate(${-25 * scale}px, ${-25 * scale}px)` }} />
<div style={{ position: 'absolute', width: 50 * this.state.scale, height: 50 * this.state.scale, backgroundColor: 'black', transform: `translate(${p1.x * 500}px, ${p1.y * 500}px) translate(${25 * scale}px, ${25 * scale}px)` }} />
+ <div
+ ref={this.setViewReference}
+ style={{
+ position: 'absolute',
+ width: 50 * this.state.scale,
+ height: 50 * this.state.scale,
+ backgroundColor: 'red',
+ zIndex: 22222,
+ transform: `translate(${p1.x * 500}px, ${p1.y * 600}px) translate(${25 * scale}px, ${25
+ * scale}px)`
+ }}
+ />
{/* Axes */}
<div style={{ position: 'absolute', width: 1, height: 500, backgroundColor: 'red', transform: 'translateX(250px)' }} />
<div style={{ position: 'absolute', width: 500, height: 1, backgroundColor: 'red', transform: 'translateY(250px)' }} />
</InteractiveDiv>;
}
}
const container = document.createElement('div');
document.body.appendChild(container);
ReactDOM.render(<App />, container);
And as you can see its no longer possible to pan while clicking the red element:
Also interesting:
@blaues0cke
Good detective work :) hmm yes, it makes sense that the event that gets added first is fired first.
As a workaround I can think of two things:
- Have a prop that allows you to cancel the pan start event (
onPanStartCancel={() => {...}}
?) - Have a method on the React element to re-register the event handlers:
panAndZoomElement.reregisterEventHandlers();
What do you think?
@woutervh- I think the second option would be the better approach since this would not require that the parent exactly knows its childs, right? (since otherwise we would have something like: onPanStartCancel={() => { /* iterate all childs + do nasty x/y checks */ }}
)?
In version 2.1.6
there is a reregisterEventHandlers
method on the component.
Thank you very much. 🔥