Skip to content

Commit

Permalink
Added distance (#35) and shouldCancelStart (#47, #36, #41) props.…
Browse files Browse the repository at this point in the history
… Prevent right click from causing sort start (#46)
  • Loading branch information
Clauderic Demers committed Sep 5, 2016
1 parent 8577046 commit dd9bcf1
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 24 deletions.
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,25 @@ More code examples are available [here](https://github.com/clauderic/react-sorta
### Prop Types

#### SortableContainer HOC
| Property | Type | Default | Description |
|:---------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| axis | String | `y` | The axis you want to sort on, either 'x' or 'y' |
| lockAxis | String | | If you'd like, you can lock movement to an axis while sorting. This is not something that is possible with HTML5 Drag & Drop |
| helperClass | String | | You can provide a class you'd like to add to the sortable helper to add some styles to it |
| transitionDuration | Number | `300` | The duration of the transition when elements shift positions. Set this to `0` if you'd like to disable transitions |
| pressDelay | Number | `0` | If you'd like elements to only become sortable after being pressed for a certain time, change this property. A good sensible default value for mobile is `200` |
| onSortStart | Function | | Callback that get's invoked when sorting begins. `function({node, index, collection}, event)` |
| onSortMove | Function | | Callback that get's invoked during sorting as the cursor moves. `function(event)` |
| onSortEnd | Function | | Callback that get's invoked when sortin ends. `function({oldIndex, newIndex, collection}, e)` |
| useDragHandle | Boolean | `false` | If you're using the `SortableHandle` HOC, set this to `true` |
| useWindowAsScrollContainer | Boolean | `false` | If you want, you can set the `window` as the scrolling container |
| hideSortableGhost | Boolean | `true` | Whether to auto-hide the ghost element. By default, as a convenience, React Sortable List will automatically hide the element that is currently being sorted. Set this to false if you would like to apply your own styling. |
| lockToContainerEdges | Boolean | `false` | You can lock movement of the sortable element to it's parent `SortableContainer` |
| lockOffset | `OffsetValue`\* \| [`OffsetValue`\*, `OffsetValue`\*] | `"50%"` | When `lockToContainerEdges` is set to `true`, this controls the offset distance between the sortable helper and the top/bottom edges of it's parent `SortableContainer`. Percentage values are relative to the height of the item currently being sorted. If you wish to specify different behaviours for locking to the *top* of the container vs the *bottom*, you may also pass in an `array` (For example: `["0%", "100%"]`). |
| getContainer | Function | | Optional function to return the scrollable container element. This property defaults to the `SortableContainer` element itself or (if `useWindowAsScrollContainer` is true) the window. Use this function to specify a custom container object (eg this is useful for integrating with certain 3rd party components such as `FlexTable`). This function is passed a single parameter (the `wrappedInstance` React element) and it is expected to return a DOM element. |

\* `OffsetValue` is either a finite `Number` or a `String` made-up of a number and a unit (`px` or `%`).
Examples: `10` (is the same as `"10px"`), `"50%"`
| Property | Type | Default | Description |
|:---------------------------|:------------------|:-----------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| axis | String | `y` | The axis you want to sort on, either 'x' or 'y' |
| lockAxis | String | | If you'd like, you can lock movement to an axis while sorting. This is not something that is possible with HTML5 Drag & Drop |
| helperClass | String | | You can provide a class you'd like to add to the sortable helper to add some styles to it |
| transitionDuration | Number | `300` | The duration of the transition when elements shift positions. Set this to `0` if you'd like to disable transitions |
| pressDelay | Number | `0` | If you'd like elements to only become sortable after being pressed for a certain time, change this property. A good sensible default value for mobile is `200`. Cannot be used in conjunction with the `distance` prop. |
| distance | Number | `0` | If you'd like elements to only become sortable after being dragged a certain number of pixels. Cannot be used in conjunction with the `pressDelay` prop. |
| shouldCancelStart | Function | [Function](https://github.com/clauderic/react-sortable-hoc/blob/master/src/SortableContainer/index.js#L34) | This function get's invoked before sorting begins, and can be used to programatically cancel sorting before it begins. By default, it will cancel sorting if the event target is either an `input`, `textarea`, `select` or `option`. |
| onSortStart | Function | | Callback that get's invoked when sorting begins. `function({node, index, collection}, event)` |
| onSortMove | Function | | Callback that get's invoked during sorting as the cursor moves. `function(event)` |
| onSortEnd | Function | | Callback that get's invoked when sorting ends. `function({oldIndex, newIndex, collection}, e)` |
| useDragHandle | Boolean | `false` | If you're using the `SortableHandle` HOC, set this to `true` |
| useWindowAsScrollContainer | Boolean | `false` | If you want, you can set the `window` as the scrolling container |
| hideSortableGhost | Boolean | `true` | Whether to auto-hide the ghost element. By default, as a convenience, React Sortable List will automatically hide the element that is currently being sorted. Set this to false if you would like to apply your own styling. |
| lockToContainerEdges | Boolean | `false` | You can lock movement of the sortable element to it's parent `SortableContainer` |
| lockOffset | `OffsetValue`\* \ | [`OffsetValue`\*, `OffsetValue`\*] | `"50%"` | When `lockToContainerEdges` is set to `true`, this controls the offset distance between the sortable helper and the top/bottom edges of it's parent `SortableContainer`. Percentage values are relative to the height of the item currently being sorted. If you wish to specify different behaviours for locking to the *top* of the container vs the *bottom*, you may also pass in an `array` (For example: `["0%", "100%"]`). |
| getContainer | Function | | Optional function to return the scrollable container element. This property defaults to the `SortableContainer` element itself or (if `useWindowAsScrollContainer` is true) the window. Use this function to specify a custom container object (eg this is useful for integrating with certain 3rd party components such as `FlexTable`). This function is passed a single parameter (the `wrappedInstance` React element) and it is expected to return a DOM element. |

\* `OffsetValue` can either be a finite `Number` or a `String` made up of a number and a unit (`px` or `%`).
Examples: `10` (which is the same as `"10px"`), `"50%"`

Expand Down
62 changes: 57 additions & 5 deletions src/SortableContainer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,49 @@ import invariant from 'invariant';
// Export Higher Order Sortable Container Component
export default function SortableContainer(WrappedComponent, config = {withRef: false}) {
return class extends Component {
constructor() {
constructor(props) {
super();
this.manager = new Manager();
this.events = {
start: this.handleStart,
move: this.cancel,
end: this.cancel
move: this.handleMove,
end: this.handleEnd
};

invariant(
!(props.distance && props.pressDelay),
'Attempted to set both `pressDelay` and `distance` on SortableContainer, you may only use one or the other, not both at the same time.'
)
}
static displayName = (WrappedComponent.displayName) ? `SortableList(${WrappedComponent.displayName})` : 'SortableList';
static WrappedComponent = WrappedComponent;
static defaultProps = {
axis: 'y',
transitionDuration: 300,
pressDelay: 0,
distance: 0,
useWindowAsScrollContainer: false,
hideSortableGhost: true,
contentWindow: (typeof window !== 'undefined') ? window : null,
contentWindow: typeof window !== 'undefined' ? window : null,
shouldCancelStart: function (e) {
if (['input', 'textarea', 'select', 'option'].indexOf(e.target.tagName.toLowerCase()) !== -1) {
return true;
}
},
lockToContainerEdges: false,
lockOffset: '50%'
};
static propTypes = {
axis: PropTypes.oneOf(['x', 'y']),
distance: PropTypes.number,
lockAxis: PropTypes.string,
helperClass: PropTypes.string,
transitionDuration: PropTypes.number,
contentWindow: PropTypes.any,
onSortStart: PropTypes.func,
onSortMove: PropTypes.func,
onSortEnd: PropTypes.func,
shouldCancelStart: PropTypes.func,
pressDelay: PropTypes.number,
useDragHandle: PropTypes.bool,
useWindowAsScrollContainer: PropTypes.bool,
Expand Down Expand Up @@ -76,6 +89,16 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
}
}
handleStart = (e) => {
const {distance, shouldCancelStart} = this.props;

if (e.button === 2 || shouldCancelStart(e)) { return false; }

this._touched = true;
this._pos = {
x: e.clientX,
y: e.clientY
};

let node = closest(e.target, (el) => el.sortableInfo != null);

if (node && !this.state.sorting && node.sortableInfo) {
Expand All @@ -85,9 +108,36 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
if (useDragHandle && !closest(e.target, (el) => el.sortableHandle != null)) return;

this.manager.active = {index, collection};
this.pressTimer = setTimeout(() => this.handlePress(e), this.props.pressDelay);

if (!distance) {
this.pressTimer = setTimeout(() => this.handlePress(e), this.props.pressDelay);
}
}
};
handleMove = (e) => {
const {distance} = this.props;

if (!this.state.sorting && this._touched) {
this._delta = {
x: this._pos.x - e.clientX,
y: this._pos.y - e.clientY
};
let delta = Math.abs(this._delta.x) + Math.abs(this._delta.y);

if (!distance) {
this.cancel();
} else if (delta >= distance) {
this.handlePress(e);
}
}
}
handleEnd = () => {
const {distance} = this.props;

this._touched = false;

if (!distance) { this.cancel(); }
}
cancel = () => {
if (!this.state.sorting) {
clearTimeout(this.pressTimer);
Expand Down Expand Up @@ -216,6 +266,8 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
sorting: false,
sortingIndex: null
});

this._touched = false;
}
getEdgeOffset(edge, node, offset = 0) {
// Get the actual offsetTop / offsetLeft value, no matter how deep the node is nested
Expand Down

0 comments on commit dd9bcf1

Please sign in to comment.