-
-
Save flushentitypacket/7717cb30d1b172e633cea864eeb4d2e7 to your computer and use it in GitHub Desktop.
import * as React from 'react' | |
type DragScrollProvisions = { | |
onMouseDown: React.MouseEventHandler<HTMLElement>, | |
ref: React.Ref<HTMLElement>, | |
} | |
export type Props = { | |
children: (provisions: DragScrollProvisions) => React.ReactNode, | |
} | |
export type PrivateState = { | |
isMouseDown: boolean, | |
lastMousePosition: number | null, | |
} | |
// TODO: Right now only supports x-direction scrolling, but can easily be expanded someday to also support y-direction | |
export class DragScrollProvider extends React.Component<Props, {}> { | |
// Not using React state since we don't want to rerender on these state changes | |
private privateState: PrivateState = { | |
isMouseDown: false, | |
lastMousePosition: null, | |
} | |
private clearListeners: Array<() => void> = [] | |
private refElement: HTMLElement | null = null | |
public render() { | |
return this.props.children({ | |
onMouseDown: this.provisionOnMouseDown, | |
ref: this.provisionRef, | |
}) | |
} | |
public componentDidMount() { | |
const onMouseUp = () => { | |
this.setPrivateState({ | |
isMouseDown: false, | |
lastMousePosition: null, | |
}) | |
} | |
document.documentElement.addEventListener('mouseup', onMouseUp) | |
const clearMouseUpListener = () => removeEventListener('mouseup', onMouseUp) | |
this.clearListeners.push(clearMouseUpListener) | |
const onMouseMove = (event: MouseEvent) => { | |
const {isMouseDown, lastMousePosition} = this.privateState | |
if (!isMouseDown) return | |
if (this.refElement === null) return | |
// The mousedown handler should have set the lastMousePosition, so this case should only happen if setPrivateState | |
// hasn't finished yet. In that case, let's just ignore this first movement and wait for that initial | |
// setPrivateState to complete. | |
if (lastMousePosition === null) return | |
this.refElement.scrollLeft += lastMousePosition - event.clientX | |
this.setPrivateState({lastMousePosition: event.clientX}) | |
} | |
document.documentElement.addEventListener('mousemove', onMouseMove) | |
const clearMouseMoveListener = () => removeEventListener('mousemove', onMouseMove) | |
this.clearListeners.push(clearMouseMoveListener) | |
} | |
public componentWillUnmount() { | |
this.clearListeners.forEach((clear) => clear()) | |
} | |
private provisionOnMouseDown: React.MouseEventHandler<HTMLElement> = (event) => { | |
this.setPrivateState({ | |
isMouseDown: true, | |
lastMousePosition: event.clientX, | |
}) | |
} | |
private provisionRef: React.Ref<HTMLElement> = (element: HTMLElement) => this.refElement = element | |
private setPrivateState = (state: Partial<PrivateState>) => { | |
this.privateState = {...this.privateState, ...state} | |
} | |
} |
import * as React from 'react' | |
import {DragScrollProvider} from './DragScrollProvider' | |
const MyComponent: React.SFC = () => ( | |
<DragScrollProvider> | |
{({onMouseDown, ref}) => ( | |
<div className='scrollableDiv' onMouseDown={onMouseDown} ref={ref}> | |
<div className='overflowsTheParent' /> | |
</div> | |
)} | |
</DragScrollProvider> | |
) |
๐ And same question
Still debugging it, but I'm able to occasionally get it stuck in the mouseDown: true position, even though I'm not holding down the mouse button.
How to recreate it, I'm not exactly sure.. but it's something like "click the mouse as quickly as possible while moving it at the same time."
@davidsa - on a child onClick
handler, add e.stopPropagation()
to stop the event from making to your DragScrollProvider
I had to convert this to flow
for my usage. I also made a few small changes. I've posted my version here: https://gist.github.com/mreishus/9c368cb01d7dbb367202425384e19891
Sorry everyone, I don't get notifications for Gists so I didn't know there were folks asking questions here! (EDIT: Apparently the notifications thing is a known issue)
@mreishus Thanks for the flux conversion and advice provided. I've been able to reproduce the issue you're talking about too, but haven't figured out what to do about it. Did you get a fix working? I have a suspicion that the mouseup
event isn't always firing. I believe the browser is supposed to implement it such that the event fires even when occurring outside the window, but it seems like not all browsers have done so.
Here's a version I made using Hooks. No TS, sorry.
import { useState, useEffect, useRef } from 'react';
export default function useDragScroll() {
const [isMouseDown, setIsMouseDown] = useState(false);
const [lastMousePosition, setLastMousePosition] = useState(null);
const ref = useRef(null);
function onMouseDown(e) {
setIsMouseDown(true);
setLastMousePosition(e.clientX);
}
useEffect(() => {
function onMouseUp() {
setIsMouseDown(false);
setLastMousePosition(null);
}
function onMouseMove(e) {
if (!isMouseDown) return;
if (ref.current === null) return;
if (lastMousePosition === null) return;
ref.current.scrollLeft += lastMousePosition - e.clientX;
setLastMousePosition(e.clientX);
}
window.addEventListener('mouseup', onMouseUp);
window.addEventListener('mousemove', onMouseMove);
return () => {
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mousemove', onMouseMove);
};
}, [isMouseDown, lastMousePosition]);
return {
ref,
onMouseDown,
};
}
function Example() {
const dragProps = useDragScroll();
return (
<div {...dragProps}>drag me bb</div>
)
}
@madisonbullard perfect, thanks ๐
how do you avoid conflicts when child elements implement
onClick