Skip to content

Commit

Permalink
Update packages/compose/src/hooks/use-dragging/README.md
Browse files Browse the repository at this point in the history
Co-Authored-By: Enrique Piqueras <[email protected]> (+13 squashed commits)
Squashed commits:
[35d3443b82] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[0666388f69] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[7291bd40d4] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[e4cc24e24c] Update packages/compose/src/hooks/use-dragging/index.js

Co-Authored-By: Enrique Piqueras <[email protected]>
[10e835bdbc] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[72607b4dc0] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[e7a80b3b13] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[75fa7962d2] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[945b498921] Update packages/compose/src/hooks/use-dragging/README.md

Co-Authored-By: Enrique Piqueras <[email protected]>
[f103de2ea7] Update packages/components/src/angle-picker/stories/index.js

Co-Authored-By: Enrique Piqueras <[email protected]>
[e572887] Update snapshot
[0f502ac] Add documentation to the useDragging hook
[97455c2] Add AnglePicker Component; Add useDragging hook
  • Loading branch information
jorgefilipecosta committed Jan 23, 2020
1 parent dcb08f3 commit ed3f482
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 0 deletions.
103 changes: 103 additions & 0 deletions packages/components/src/angle-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* WordPress dependencies
*/
import { useRef } from '@wordpress/element';
import { useInstanceId, __experimentalUseDragging as useDragging } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import BaseControl from '../base-control';

function getAngle( centerX, centerY, pointX, pointY ) {
const y = pointY - centerY;
const x = pointX - centerX;

const angleInRadians = Math.atan2( y, x );
const angleInDeg = Math.round( angleInRadians * ( 180 / Math.PI ) ) + 90;
if ( angleInDeg < 0 ) {
return 360 + angleInDeg;
}
return angleInDeg;
}

const AngleCircle = ( { value, onChange, ...props } ) => {
const angleCircleRef = useRef();
const angleCircleCenter = useRef();

const setAngleCircleCenter = () => {
const rect = angleCircleRef.current.getBoundingClientRect();
angleCircleCenter.current = {
x: rect.x + ( rect.width / 2 ),
y: rect.y + ( rect.height / 2 ),
};
};

const changeAngleToPosition = ( event ) => {
const { x: centerX, y: centerY } = angleCircleCenter.current;
onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) );
};

const { startDrag, isDragging } = useDragging( {
onDragStart: ( event ) => {
setAngleCircleCenter();
changeAngleToPosition( event );
},
onDragMove: changeAngleToPosition,
onDragEnd: changeAngleToPosition,
} );
return (
/* eslint-disable jsx-a11y/no-static-element-interactions */
<div
ref={ angleCircleRef }
onMouseDown={ startDrag }
className="components-angle-picker__angle-circle"
style={ isDragging ? { cursor: 'grabbing' } : undefined }
{ ...props }
>
<div
style={ value ? { transform: `rotate(${ value }deg)` } : undefined }
className="components-angle-picker__angle-circle-indicator-wrapper"
>
<span className="components-angle-picker__angle-circle-indicator" />
</div>
</div>
/* eslint-enable jsx-a11y/no-static-element-interactions */
);
};

export default function AnglePicker( { value, onChange, label = __( 'Angle' ) } ) {
const instanceId = useInstanceId( AnglePicker );
const inputId = `components-angle-picker__input-${ instanceId }`;
return (
<BaseControl
label={ label }
id={ inputId }
className="components-angle-picker"
>
<AngleCircle
value={ value }
onChange={ onChange }
aria-hidden="true"
/>
<input
className="components-angle-picker__input-field"
type="number"
id={ inputId }
onChange={ ( event ) => {
const unprocessedValue = event.target.value;
const inputValue = unprocessedValue !== '' ?
parseInt( event.target.value, 10 ) :
0;
onChange( inputValue );
} }
value={ value }
min={ 0 }
max={ 360 }
step="1"
/>
</BaseControl>
);
}

23 changes: 23 additions & 0 deletions packages/components/src/angle-picker/stories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import AnglePicker from '../';

export default { title: 'Components|AnglePicker', component: AnglePicker };

const AnglePickerWithState = () => {
const [ angle, setAngle ] = useState();
return (
<AnglePicker value={ angle } onChange={ setAngle } />
);
};

export const _default = () => {
return <AnglePickerWithState />;
};
42 changes: 42 additions & 0 deletions packages/components/src/angle-picker/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.components-angle-picker {
width: 50%;
&.components-base-control .components-base-control__label {
display: block;
}
}

.components-angle-picker__input-field {
width: calc(100% - #{$icon-button-size});
max-width: 100px;
}

.components-angle-picker__angle-circle {
width: $icon-button-size - ( 2 * $grid-size-small );
height: $icon-button-size - ( 2 * $grid-size-small );
border: 2px solid $dark-gray-500;
border-radius: 50%;
float: left;
margin-right: $grid-size-small;
cursor: grab;
}

.components-angle-picker__angle-circle-indicator-wrapper {
position: relative;
width: 100%;
height: 100%;
}

.components-angle-picker__angle-circle-indicator {
width: 1px;
height: 1px;
border-radius: 50%;
border: 3px solid $dark-gray-500;
display: block;
position: absolute;
top: -($icon-button-size - (2 * $grid-size-small)) / 2;
bottom: 0;
left: 0;
right: 0;
margin: auto;
background: $dark-gray-500;
}
1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { SVG, Path, Circle, Polygon, Rect, G, HorizontalRule, BlockQuotation } f

// Components
export { default as Animate } from './animate';
export { default as __experimentalAnglePicker } from './angle-picker';
export { default as Autocomplete } from './autocomplete';
export { default as BaseControl } from './base-control';
export { default as Button } from './button';
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "./animate/style.scss";
@import "./angle-picker/style.scss";
@import "./autocomplete/style.scss";
@import "./base-control/style.scss";
@import "./button-group/style.scss";
Expand Down
88 changes: 88 additions & 0 deletions packages/compose/src/hooks/use-dragging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
`useDragging`
==============

In some situations, we want to have simple drag & drop behaviors.
Typically drag & drop behaviors follow a common pattern: We have an element that we want to drag or where we want dragging to start; the dragging starts when the `onMouseDown` event happens on the target element. When the dragging starts, global event listeners for mouse movement (`mousemove`) and the mouse up event (`mouseup`) are added. When the global mouse movement event triggers, the dragging behavior happens (e.g., a position is updated), when the mouse up event triggers, dragging stops, and both global event listeners are removed.
`useDragging` makes the implementation of the described common pattern simpler because it handles the addition and removal of global events.

## Input Object Properties

### `onDragStart`

- Type: `Function`

The hook calls `onDragStart` when the dragging starts. The function receives as parameters the same parameters passed to `startDrag` whose documentation is available below.
If `startDrag` is passed directly as an `onMouseDown` event handler, `onDragStart` will receive the `onMouseDown` event.

### `onDragMove`

- Type: `Function`

The hook calls `onDragMove ` after the dragging starts and when a mouse movement happens.
It receives the `mousemove` event.

### `onDragEnd`

- Type: `Function`

The hook calls `onDragEnd` when the dragging ends. When dragging is explicitly stopped, the function receives as parameters, the same parameters passed to `endDrag` whose documentation is available below.
When dragging stops because the user releases the mouse, the function receives the `mouseup` event.

## Return Object Properties

### `startDrag`

- Type: `Function`

A function that, when called, starts the dragging behavior. Parameters passed to this function will be passed to `onDragStart` when the dragging starts.
It is possible to directly pass `startDrag` as the `onMouseDown` event handler of some element.

### `endDrag`

- Type: `Function`

A function that, when called, stops the dragging behavior. Parameters passed to this function will be passed to `onDragEnd` when the dragging ends.
In most cases, there is no need to call this function directly. Dragging behavior automatically stops when the mouse is released.

### `isDragging`

- Type: `Boolean`

A boolean value, when true it means dragging is currently taking place; when false, it means dragging is not taking place.

## Usage
The following example allows us to drag & drop a red square around the entire viewport.

```jsx
/**
* WordPress dependencies
*/
import { useState, useCallback } from '@wordpress/element';
import { __experimentalUseDragging as useDragging } from '@wordpress/compose';


const UseDraggingExample = () => {
const [ position, setPosition ] = useState( null );
const changePosition = useCallback(
( event ) => {
setPosition( { x: event.clientX, y: event.clientY } );
}
);
const { startDrag } = useDragging( {
onDragMove: changePosition,
} );
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
onMouseDown={ startDrag }
style={ {
position: 'fixed',
width: 10,
height: 10,
backgroundColor: 'red',
...( position ? { top: position.y, left: position.x } : {} ),
} }
/>
);
};
```
74 changes: 74 additions & 0 deletions packages/compose/src/hooks/use-dragging/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* WordPress dependencies
*/
import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from '@wordpress/element';

const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;

export default function useDragging( { onDragStart, onDragMove, onDragEnd } ) {
const [ isDragging, setIsDragging ] = useState( false );

const eventsRef = useRef( {
onDragStart,
onDragMove,
onDragEnd,
} );
useIsomorphicLayoutEffect(
() => {
eventsRef.current.onDragStart = onDragStart;
eventsRef.current.onDragMove = onDragMove;
eventsRef.current.onDragEnd = onDragEnd;
},
[ onDragStart, onDragMove, onDragEnd ]
);

const onMouseMove = useCallback(
( ...args ) => ( eventsRef.current.onDragMove && eventsRef.current.onDragMove( ...args ) ),
[]
);
const endDrag = useCallback(
( ...args ) => {
if ( eventsRef.current.onDragEnd ) {
eventsRef.current.onDragEnd( ...args );
}
document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', endDrag );
setIsDragging( false );
},
[]
);
const startDrag = useCallback(
( ...args ) => {
if ( eventsRef.current.onDragStart ) {
eventsRef.current.onDragStart( ...args );
}
document.addEventListener( 'mousemove', onMouseMove );
document.addEventListener( 'mouseup', endDrag );
setIsDragging( true );
},
[]
);

// Remove the global events when unmounting if needed.
useEffect( () => {
return () => {
if ( isDragging ) {
document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', endDrag );
}
};
}, [ isDragging ] );

return {
startDrag,
endDrag,
isDragging,
};
}
1 change: 1 addition & 0 deletions packages/compose/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { default as withSafeTimeout } from './higher-order/with-safe-timeout';
export { default as withState } from './higher-order/with-state';

// Hooks
export { default as __experimentalUseDragging } from './hooks/use-dragging';
export { default as useInstanceId } from './hooks/use-instance-id';
export { default as useKeyboardShortcut } from './hooks/use-keyboard-shortcut';
export { default as useMediaQuery } from './hooks/use-media-query';
Expand Down
Loading

0 comments on commit ed3f482

Please sign in to comment.