Skip to content

Commit 24d00b8

Browse files
committed
Add option to specify locking offsets of the helper in the container. (fixes clauderic#21)
1 parent cbeda69 commit 24d00b8

File tree

3 files changed

+82
-8
lines changed

3 files changed

+82
-8
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ More code examples are available [here](https://github.com/clauderic/react-sorta
102102
| useWindowAsScrollContainer | Boolean | `false` | If you want, you can set the `window` as the scrolling container |
103103
| 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. |
104104
| lockToContainerEdges | Boolean | `false` | You can lock movement of the sortable element to it's parent `SortableContainer` |
105+
| 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%"]`). |
106+
107+
\* `OffsetValue` is either a finite `Number` or a `String` made-up of a number and a unit (`px` or `%`).
108+
Examples: `10` (is the same as `"10px"`), `"50%"`
105109

106110
#### SortableElement HOC
107111
| Property | Type | Default | Required? | Description |

src/SortableContainer/index.js

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {Component, PropTypes} from 'react';
22
import ReactDOM from 'react-dom';
33
import Manager from '../Manager';
4-
import {closest, events, vendorPrefix} from '../utils';
4+
import {closest, events, vendorPrefix, limit} from '../utils';
55
import invariant from 'invariant';
66

77
// Export Higher Order Sortable Container Component
@@ -25,7 +25,8 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
2525
useWindowAsScrollContainer: false,
2626
hideSortableGhost: true,
2727
contentWindow: window,
28-
lockToContainerEdges: false
28+
lockToContainerEdges: false,
29+
lockOffset: '50%',
2930
};
3031
static propTypes = {
3132
axis: PropTypes.string,
@@ -40,7 +41,12 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
4041
useDragHandle: PropTypes.bool,
4142
useWindowAsScrollContainer: PropTypes.bool,
4243
hideSortableGhost: PropTypes.bool,
43-
lockToContainerEdges: PropTypes.bool
44+
lockToContainerEdges: PropTypes.bool,
45+
lockOffset: PropTypes.oneOfType([
46+
PropTypes.number,
47+
PropTypes.string,
48+
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string]))
49+
]),
4450
};
4551
static childContextTypes = {
4652
manager: PropTypes.object.isRequired
@@ -218,6 +224,56 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
218224
y: (e.touches) ? e.touches[0].clientY : e.clientY
219225
}
220226
}
227+
getLockPixelOffsets() {
228+
let {lockOffset} = this.props;
229+
230+
if (!Array.isArray(lockOffset)) {
231+
lockOffset = [lockOffset, lockOffset];
232+
}
233+
invariant(
234+
lockOffset.length === 2,
235+
'lockOffset prop of SortableContainer should be a single ' +
236+
'value or an array of exactly two values. Given %s',
237+
lockOffset
238+
);
239+
240+
const [minLockOffset, maxLockOffset] = lockOffset;
241+
242+
return [
243+
this.getLockPixelOffset(minLockOffset),
244+
this.getLockPixelOffset(maxLockOffset),
245+
];
246+
}
247+
getLockPixelOffset(lockOffset) {
248+
let offset = lockOffset;
249+
let unit = 'px';
250+
251+
if (typeof lockOffset === 'string') {
252+
const match = /^[+-]?\d*(?:\.\d*)?(px|%)$/.exec(lockOffset);
253+
254+
invariant(
255+
match !== null,
256+
'lockOffset value should be a number or a string of a ' +
257+
'number followed by "px" or "%". Given %s',
258+
lockOffset
259+
);
260+
261+
offset = parseFloat(lockOffset);
262+
unit = match[1];
263+
}
264+
265+
invariant(
266+
isFinite(offset),
267+
'lockOffset value should be a finite. Given %s',
268+
lockOffset
269+
);
270+
271+
if (unit === '%') {
272+
offset = offset * this.dimension / 100;
273+
}
274+
275+
return offset;
276+
}
221277
updatePosition(e) {
222278
let {axis, lockAxis, lockToContainerEdges} = this.props;
223279
let offset = this.getOffset(e);
@@ -229,11 +285,15 @@ export default function SortableContainer(WrappedComponent, config = {withRef: f
229285
this.translate = translate[axis];
230286

231287
if (lockToContainerEdges) {
232-
if (translate[axis] < this.minTranslate) {
233-
translate[axis] = this.minTranslate;
234-
} else if (translate[axis] > this.maxTranslate) {
235-
translate[axis] = this.maxTranslate;
236-
}
288+
const [minLockOffset, maxLockOffset] = this.getLockPixelOffsets();
289+
const minOffset = this.dimension / 2 - minLockOffset;
290+
const maxOffset = this.dimension / 2 - maxLockOffset;
291+
292+
translate[axis] = limit(
293+
this.minTranslate + minOffset,
294+
this.maxTranslate - maxOffset,
295+
translate[axis]
296+
);
237297
}
238298

239299
switch (lockAxis) {

src/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ export function closest(el, fn) {
3737
el = el.parentNode;
3838
}
3939
}
40+
41+
export function limit(min, max, value) {
42+
if (value < min) {
43+
return min;
44+
}
45+
if (value > max) {
46+
return max;
47+
}
48+
return value;
49+
}

0 commit comments

Comments
 (0)