Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(drag): add useDrag hook #902

Merged
merged 13 commits into from
Nov 5, 2020
Merged
3 changes: 2 additions & 1 deletion packages/visx-demo/src/pages/docs/drag.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import DragReadme from '!!raw-loader!../../../../visx-drag/Readme.md';
import Drag from '../../../../visx-drag/src/Drag';
import useDrag from '../../../../visx-drag/src/useDrag';
import DocPage from '../../components/DocPage';
import DragITile from '../../components/Gallery/DragITile';
import DragIITile from '../../components/Gallery/DragIITile';

const components = [Drag];
const components = [useDrag, Drag];

const examples = [DragITile, DragIITile];

Expand Down
40 changes: 19 additions & 21 deletions packages/visx-demo/src/sandboxes/visx-drag-i/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,25 @@ export default function DragI({ width, height }: DragIProps) {
setDraggingItems(raise(draggingItems, i));
}}
>
{({ dragStart, dragEnd, dragMove, isDragging, dx, dy }) =>
(false && isDragging && console.log(d.id, d.x, dx)) || (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just removed this guy 😱

<circle
key={`dot-${d.id}`}
cx={d.x}
cy={d.y}
r={isDragging ? d.radius + 4 : d.radius}
fill={isDragging ? 'url(#stroke)' : colorScale(d.id)}
transform={`translate(${dx}, ${dy})`}
fillOpacity={0.9}
stroke={isDragging ? 'white' : 'transparent'}
strokeWidth={2}
onMouseMove={dragMove}
onMouseUp={dragEnd}
onMouseDown={dragStart}
onTouchStart={dragStart}
onTouchMove={dragMove}
onTouchEnd={dragEnd}
/>
)
}
{({ dragStart, dragEnd, dragMove, isDragging, dx, dy }) => (
<circle
key={`dot-${d.id}`}
cx={d.x}
cy={d.y}
r={isDragging ? d.radius + 4 : d.radius}
fill={isDragging ? 'url(#stroke)' : colorScale(d.id)}
transform={`translate(${dx}, ${dy})`}
fillOpacity={0.9}
stroke={isDragging ? 'white' : 'transparent'}
strokeWidth={2}
onMouseMove={dragMove}
onMouseUp={dragEnd}
onMouseDown={dragStart}
onTouchStart={dragStart}
onTouchMove={dragMove}
onTouchEnd={dragEnd}
/>
)}
</Drag>
))}
</svg>
Expand Down
115 changes: 61 additions & 54 deletions packages/visx-demo/src/sandboxes/visx-drag-ii/Example.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { LinePath } from '@visx/shape';
import { Drag } from '@visx/drag';
import { useDrag } from '@visx/drag';
import { curveBasis } from '@visx/curve';
import { LinearGradient } from '@visx/gradient';

Expand All @@ -15,6 +15,31 @@ export type DragIIProps = {

export default function DragII({ data = [], width, height }: DragIIProps) {
const [lines, setLines] = useState<Lines>(data);
const onDragStart = useCallback(
currDrag => {
// add the new line with the starting point
setLines(currLines => [...currLines, [{ x: currDrag.x, y: currDrag.y }]]);
},
[setLines],
);
const onDragMove = useCallback(
currDrag => {
// add the new point to the current line
setLines(currLines => {
const nextLines = [...currLines];
const newPoint = { x: currDrag.x + currDrag.dx, y: currDrag.y + currDrag.dy };
const lastIndex = nextLines.length - 1;
nextLines[lastIndex] = [...(nextLines[lastIndex] || []), newPoint];
return nextLines;
});
},
[setLines],
);
const { x = 0, y = 0, dx, dy, isDragging, dragStart, dragEnd, dragMove } = useDrag({
onDragStart,
onDragMove,
resetOnStart: true,
});

return width < 10 ? null : (
<div className="DragII" style={{ touchAction: 'none' }}>
Expand All @@ -33,63 +58,45 @@ export default function DragII({ data = [], width, height }: DragIIProps) {
y={d => d.y}
/>
))}
<Drag
width={width}
height={height}
resetOnStart
onDragStart={({ x = 0, y = 0 }) => {
// add the new line with the starting point
setLines(currLines => [...currLines, [{ x, y }]]);
}}
onDragMove={({ x = 0, y = 0, dx, dy }) => {
// add the new point to the current line
setLines(currLines => {
const nextLines = [...currLines];
const newPoint = { x: x + dx, y: y + dy };
const lastIndex = nextLines.length - 1;
nextLines[lastIndex] = [...(nextLines[lastIndex] || []), newPoint];
return nextLines;
});
}}
>
{({ x = 0, y = 0, dx, dy, isDragging, dragStart, dragEnd, dragMove }) => (

<g>
{isDragging && (
/* capture mouse events (note: <Drag /> does this for you) */
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 this is the key missing feature from useDrag

<rect
width={width}
height={height}
onMouseMove={dragMove}
onMouseUp={dragEnd}
fill="transparent"
/>
)}
{/* decorate the currently drawing line */}
{isDragging && (
<g>
{/* decorate the currently drawing line */}
{isDragging && (
<g>
<rect
fill="white"
width={8}
height={8}
x={x + dx - 4}
y={y + dy - 4}
pointerEvents="none"
/>
<circle
cx={x}
cy={y}
r={4}
fill="transparent"
stroke="white"
pointerEvents="none"
/>
</g>
)}
{/* create the drawing area */}
<rect
fill="transparent"
width={width}
height={height}
onMouseDown={dragStart}
onMouseUp={dragEnd}
onMouseMove={dragMove}
onTouchStart={dragStart}
onTouchEnd={dragEnd}
onTouchMove={dragMove}
fill="white"
width={8}
height={8}
x={x + dx - 4}
y={y + dy - 4}
pointerEvents="none"
/>
<circle cx={x} cy={y} r={4} fill="transparent" stroke="white" pointerEvents="none" />
</g>
)}
</Drag>
{/* create the drawing area */}
<rect
fill="transparent"
width={width}
height={height}
onMouseDown={dragStart}
onMouseUp={isDragging ? dragEnd : undefined}
onMouseMove={isDragging ? dragMove : undefined}
onTouchStart={dragStart}
onTouchEnd={isDragging ? dragEnd : undefined}
onTouchMove={isDragging ? dragMove : undefined}
/>
</g>
</svg>
<div className="deets">
<div>
Expand Down
157 changes: 31 additions & 126 deletions packages/visx-drag/src/Drag.tsx
Original file line number Diff line number Diff line change
@@ -1,139 +1,44 @@
/* eslint-disable react/jsx-handler-names */
import React from 'react';
import { localPoint } from '@visx/event';
import useDrag, { UseDrag, UseDragOptions, HandlerArgs as HandlerArgsType } from './useDrag';

type MouseOrTouchEvent = React.MouseEvent | React.TouchEvent;
export type HandlerArgs = HandlerArgsType;

export type DragProps = {
export type DragProps = UseDragOptions & {
/** Children render function which is passed the state of dragging and callbacks for drag start/end/move. */
children: (args: ChildrenArgs) => React.ReactNode;
children: (args: UseDrag) => React.ReactNode;
/** Width of the drag container. */
width: number;
/** Height of the drag container. */
height: number;
/** Whether to render an invisible rect below children to capture the drag area as defined by width and height. */
captureDragArea?: boolean;
/** Whether to reset drag state upon the start of a new drag. */
resetOnStart?: boolean;
/** Optional callback invoked upon drag end. */
onDragEnd?: (args: HandlerArgs) => void;
/** Optional callback invoked upon drag movement. */
onDragMove?: (args: HandlerArgs) => void;
/** Optional callback invoked upon drag start. */
onDragStart?: (args: HandlerArgs) => void;
};

export type DragState = {
x: number | undefined;
y: number | undefined;
dx: number;
dy: number;
isDragging: boolean;
};

export type HandlerArgs = DragState & { event: MouseOrTouchEvent };

type ChildrenArgs = DragState & {
dragEnd: (event: MouseOrTouchEvent) => void;
dragMove: (event: MouseOrTouchEvent) => void;
dragStart: (event: MouseOrTouchEvent) => void;
};

export default class Drag extends React.Component<DragProps, DragState> {
static defaultProps = {
captureDragArea: true,
resetOnStart: false,
};

state = {
x: undefined,
y: undefined,
dx: 0,
dy: 0,
isDragging: false,
};

handleDragStart = (event: MouseOrTouchEvent) => {
const { onDragStart, resetOnStart } = this.props;
event.persist();

this.setState(
({ dx, dy }) => {
const point = localPoint(event) || { x: 0, y: 0 };
return {
isDragging: true,
dx: resetOnStart ? 0 : dx,
dy: resetOnStart ? 0 : dy,
x: resetOnStart ? point.x : point.x - dx,
y: resetOnStart ? point.y : point.y - dy,
};
},
onDragStart &&
(() => {
onDragStart({ ...this.state, event });
}),
);
};

handleDragMove = (event: MouseOrTouchEvent) => {
const { onDragMove } = this.props;
event.persist();

this.setState(
({ x, y, isDragging }) => {
const point = localPoint(event) || { x: 0, y: 0 };
return isDragging
? {
isDragging: true,
dx: point.x - (x || 0),
dy: point.y - (y || 0),
}
: null;
},
onDragMove &&
(() => {
if (this.state.isDragging) onDragMove({ ...this.state, event });
}),
);
};

handleDragEnd = (event: MouseOrTouchEvent) => {
const { onDragEnd } = this.props;
event.persist();

this.setState(
{ isDragging: false },
onDragEnd &&
(() => {
onDragEnd({ ...this.state, event });
}),
);
};

render() {
const { x, y, dx, dy, isDragging } = this.state;
const { children, width, height, captureDragArea } = this.props;
return (
<>
{isDragging && captureDragArea && (
<rect
width={width}
height={height}
onMouseMove={this.handleDragMove}
onMouseUp={this.handleDragEnd}
fill="transparent"
/>
)}
{children({
x,
y,
dx,
dy,
isDragging,
dragEnd: this.handleDragEnd,
dragMove: this.handleDragMove,
dragStart: this.handleDragStart,
})}
</>
);
}
export default function Drag({
captureDragArea = true,
children,
height,
onDragEnd,
onDragMove,
onDragStart,
resetOnStart,
width,
}: DragProps) {
const drag = useDrag({ resetOnStart, onDragEnd, onDragMove, onDragStart });

return (
<>
{drag.isDragging && captureDragArea && (
<rect
width={width}
height={height}
onMouseMove={drag.dragMove}
onMouseUp={drag.dragEnd}
fill="transparent"
/>
)}
{children(drag)}
</>
);
}
1 change: 1 addition & 0 deletions packages/visx-drag/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as Drag } from './Drag';
export { default as useDrag } from './useDrag';
export { default as raise } from './util/raise';
Loading