Skip to content

Commit 9ef4123

Browse files
committed
Add horizontal direction support
1 parent 5138d17 commit 9ef4123

File tree

3 files changed

+133
-64
lines changed

3 files changed

+133
-64
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ npm i react-native-sortable-list --save
2525
- **order** (Array) an array of keys from data, the order of keys from the array will be used to initial rows order
2626
- **style** (Object, Array)
2727
- **contentContainerStyle** (Object, Array) these styles will be applied to the inner scroll view content container
28+
- **horizontal** (boolean) when true, the SortableList's children are arranged horizontally in a row instead of vertically in a column. The default value is false.
2829
- **sortingEnabled** (boolean) when false, rows are not sortable. The default value is true.
2930
- **scrollEnabled** (boolean) when false, the content does not scrollable. The default value is true.
30-
- **autoscrollAreaHeight** (number) determines the height of the area at the top and the bottom of the list that will trigger autoscrolling. Defaults to 60.<br />
31+
- **autoscrollAreaSize** (number) determines the height for vertical list and the width for horizontal list of the area at the begining and the end of the list that will trigger autoscrolling. Defaults to 60.<br />
3132
- **renderRow** (function)<br />
3233
`({key, index, data, disabled, active}) => renderable`<br />
3334
Takes a row key, row index, data entry from the data source and its statuses disabled, active and should return a renderable component to be rendered as the row.<br />

src/Row.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default class Row extends Component {
99
children: PropTypes.node,
1010
animated: PropTypes.bool,
1111
disabled: PropTypes.bool,
12+
horizontal: PropTypes.bool,
1213
style: Animated.View.propTypes.style,
1314
location: PropTypes.shape({
1415
x: PropTypes.number,
@@ -194,9 +195,9 @@ export default class Row extends Component {
194195
}
195196

196197
_mapGestureToMove(prevGestureState, gestureState) {
197-
return {
198-
dy: gestureState.moveY - prevGestureState.moveY,
199-
};
198+
return this.props.horizontal
199+
? {dx: gestureState.moveX - prevGestureState.moveX}
200+
: {dy: gestureState.moveY - prevGestureState.moveY};
200201
}
201202

202203
_isDisabled() {

src/SortableList.js

Lines changed: 127 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@ export default class SortableList extends Component {
2020
contentContainerStyle: View.propTypes.style,
2121
sortingEnabled: PropTypes.bool,
2222
scrollEnabled: PropTypes.bool,
23+
horizontal: PropTypes.bool,
2324

2425
renderRow: PropTypes.func.isRequired,
2526

2627
onChangeOrder: PropTypes.func,
2728
onActivateRow: PropTypes.func,
2829
onReleaseRow: PropTypes.func,
29-
autoscrollAreaHeight: PropTypes.number,
30+
autoscrollAreaSize: PropTypes.number,
3031
};
3132

3233
static defaultProps = {
3334
sortingEnabled: true,
3435
scrollEnabled: true,
35-
autoscrollAreaHeight: 60,
36+
autoscrollAreaSize: 60,
3637
}
3738

3839
/**
@@ -97,62 +98,79 @@ export default class SortableList extends Component {
9798
}
9899
}
99100

100-
scrollBy({dy = 0, animated = false}) {
101-
this._contentOffset = {
102-
x: this._contentOffset.x,
103-
y: this._contentOffset.y + dy,
104-
};
101+
scrollBy({dx = 0, dy = 0, animated = false}) {
102+
if (this.props.horizontal) {
103+
this._contentOffset.x += dx;
104+
} else {
105+
this._contentOffset.y += dy;
106+
}
107+
105108
this._scroll(animated);
106109
}
107110

108-
scrollTo({y = 0, animated = false}) {
109-
this._contentOffset = {
110-
x: this._contentOffset.x,
111-
y,
112-
};
111+
scrollTo({x = 0, y = 0, animated = false}) {
112+
if (this.props.horizontal) {
113+
this._contentOffset.x = x;
114+
} else {
115+
this._contentOffset.y = y;
116+
}
117+
113118
this._scroll(animated);
114119
}
115120

116121
scrollToRowKey({key, animated = false}) {
117122
const {order, containerLayout, rowsLayouts} = this.state;
118123

124+
let keyX = 0;
119125
let keyY = 0;
120126

121-
for (let rowKey of order) {
127+
for (const rowKey of order) {
122128
if (rowKey === key) {
123129
break;
124130
}
125131

132+
keyX += rowsLayouts[rowKey].width;
126133
keyY += rowsLayouts[rowKey].height;
127134
}
128135

129136
// Scroll if the row is not visible.
130137
if (
131-
keyY < this._contentOffset.y ||
132-
keyY > this._contentOffset.y + containerLayout.height
138+
this.props.horizontal
139+
? (keyX < this._contentOffset.x || keyX > this._contentOffset.x + containerLayout.width)
140+
: (keyY < this._contentOffset.y || keyY > this._contentOffset.y + containerLayout.height)
133141
) {
134-
this._contentOffset = {
135-
x: this._contentOffset.x,
136-
y: keyY,
137-
};
142+
if (this.props.horizontal) {
143+
this._contentOffset.x = keyX;
144+
} else {
145+
this._contentOffset.y = keyY;
146+
}
147+
138148
this._scroll(animated);
139149
}
140150
}
141151

142152
render() {
143-
const {contentContainerStyle} = this.props;
144-
const {contentHeight, scrollEnabled} = this.state;
145-
const containerStyle = StyleSheet.flatten([styles.container, this.props.style, this.state.style]);
153+
const {contentContainerStyle, horizontal} = this.props;
154+
const {contentHeight, contentWidth, scrollEnabled} = this.state;
155+
const containerStyle = StyleSheet.flatten([this.props.style, this.state.style]);
156+
const innerContainerStyle = [styles.container];
157+
158+
if (horizontal) {
159+
innerContainerStyle.push({width: contentWidth});
160+
} else {
161+
innerContainerStyle.push({height: contentHeight});
162+
}
146163

147164
return (
148165
<Animated.View style={containerStyle} ref={this._onRefContainer}>
149166
<ScrollView
150167
ref={this._onRefScrollView}
168+
horizontal={horizontal}
151169
contentContainerStyle={contentContainerStyle}
152170
scrollEventThrottle={2}
153171
scrollEnabled={scrollEnabled}
154172
onScroll={this._onScroll}>
155-
<View style={[styles.container, {height: contentHeight}]}>
173+
<View style={innerContainerStyle}>
156174
{this._renderRows()}
157175
</View>
158176
</ScrollView>
@@ -161,12 +179,20 @@ export default class SortableList extends Component {
161179
}
162180

163181
_renderRows() {
164-
const {sortingEnabled, renderRow} = this.props;
182+
const {horizontal, sortingEnabled, renderRow} = this.props;
165183
const {order, data, activeRowKey, releasedRowKey, rowsLayouts} = this.state;
166184

167-
const rowWidth = rowsLayouts && Math.max(
168-
...Object.keys(rowsLayouts).map((key) => rowsLayouts[key].width)
169-
);
185+
let rowHeight = 0;
186+
let rowWidth = 0;
187+
188+
if (rowsLayouts) {
189+
Object.keys(rowsLayouts).forEach((key) => {
190+
rowHeight = Math.max(rowHeight, rowsLayouts[key].height);
191+
rowWidth = Math.max(rowWidth, rowsLayouts[key].width);
192+
});
193+
}
194+
195+
let nextX = 0;
170196
let nextY = 0;
171197

172198
return order.map((key, index) => {
@@ -175,9 +201,15 @@ export default class SortableList extends Component {
175201
let resolveLayout;
176202

177203
if (rowsLayouts) {
178-
style.width = rowWidth;
179-
location.y = nextY;
180-
nextY += rowsLayouts[key].height;
204+
if (horizontal) {
205+
style.height = rowHeight;
206+
location.x = nextX;
207+
nextX += rowsLayouts[key].width;
208+
} else {
209+
style.width = rowWidth;
210+
location.y = nextY;
211+
nextY += rowsLayouts[key].height;
212+
}
181213
} else {
182214
this._rowsLayouts.push(new Promise((resolve) => (resolveLayout = resolve)));
183215
}
@@ -193,6 +225,7 @@ export default class SortableList extends Component {
193225
<Row
194226
key={uniqueRowKey(key)}
195227
ref={this._onRefRow.bind(this, key)}
228+
horizontal={horizontal}
196229
animated={this._areRowsAnimated && !active}
197230
disabled={!sortingEnabled}
198231
style={style}
@@ -221,16 +254,19 @@ export default class SortableList extends Component {
221254
this._container.measure((x, y, width, height, pageX, pageY) => {
222255
const rowsLayoutsByKey = {};
223256
let contentHeight = 0;
257+
let contentWidth = 0;
224258

225259
rowsLayouts.forEach(({rowKey, layout}) => {
226260
rowsLayoutsByKey[rowKey] = layout;
227261
contentHeight += layout.height;
262+
contentWidth += layout.width;
228263
});
229264

230265
this.setState({
231266
containerLayout: {x, y, width, height, pageX, pageY},
232267
rowsLayouts: rowsLayoutsByKey,
233268
contentHeight,
269+
contentWidth,
234270
}, () => {
235271
this._animateRowsAppearance(() => (this._areRowsAnimated = true));
236272
});
@@ -304,13 +340,16 @@ export default class SortableList extends Component {
304340
* Finds a row, which was covered with the moving row’s half.
305341
*/
306342
_findRowUnderActiveRow() {
343+
const {horizontal} = this.props;
307344
const {rowsLayouts, activeRowKey, activeRowIndex, order} = this.state;
308345
const movingRowLayout = rowsLayouts[activeRowKey];
346+
const rowLeftX = this._activeRowLocation.x
347+
const rowRightX = rowLeftX + movingRowLayout.width;
309348
const rowTopY = this._activeRowLocation.y;
310349
const rowBottomY = rowTopY + movingRowLayout.height;
311350

312351
for (
313-
let currentRowIndex = 0, y = 0, rowsCount = order.length;
352+
let currentRowIndex = 0, x = 0, y = 0, rowsCount = order.length;
314353
currentRowIndex < rowsCount - 1;
315354
currentRowIndex++
316355
) {
@@ -319,20 +358,23 @@ export default class SortableList extends Component {
319358
const nextRowIndex = currentRowIndex + 1;
320359
const nextRowLayout = rowsLayouts[order[nextRowIndex]];
321360

322-
y = y + currentRowLayout.height;
361+
x += currentRowLayout.width;
362+
y += currentRowLayout.height;
323363

324-
if (currentRowKey !== activeRowKey &&
325-
(y - currentRowLayout.height <= rowTopY || currentRowIndex === 0) &&
326-
rowTopY <= y - currentRowLayout.height / 3
327-
) {
364+
if (currentRowKey !== activeRowKey && (
365+
horizontal
366+
? ((x - currentRowLayout.width <= rowLeftX || currentRowIndex === 0) && rowLeftX <= x - currentRowLayout.width / 3)
367+
: ((y - currentRowLayout.height <= rowTopY || currentRowIndex === 0) && rowTopY <= y - currentRowLayout.height / 3)
368+
)) {
328369
return {
329370
rowKey: order[currentRowIndex],
330371
rowIndex: currentRowIndex,
331372
};
332373
}
333374

334-
if (y + nextRowLayout.height / 3 <= rowBottomY &&
335-
(rowBottomY <= y + nextRowLayout.height || nextRowIndex === rowsCount - 1)
375+
if (horizontal
376+
? (x + nextRowLayout.width / 3 <= rowRightX && (rowRightX <= x + nextRowLayout.width || nextRowIndex === rowsCount - 1))
377+
: (y + nextRowLayout.height / 3 <= rowBottomY && (rowBottomY <= y + nextRowLayout.height || nextRowIndex === rowsCount - 1))
336378
) {
337379
return {
338380
rowKey: order[nextRowIndex],
@@ -345,13 +387,22 @@ export default class SortableList extends Component {
345387
}
346388

347389
_scrollOnMove(e) {
348-
const {pageY} = e.nativeEvent;
390+
const {pageX, pageY} = e.nativeEvent;
391+
const {horizontal} = this.props;
349392
const {containerLayout} = this.state;
350-
const inAutoScrollUpArea = pageY < containerLayout.pageY + this.props.autoscrollAreaHeight;
351-
const inAutoScrollDownArea = pageY > containerLayout.pageY + containerLayout.height - this.props.autoscrollAreaHeight;
393+
let inAutoScrollBeginArea = false;
394+
let inAutoScrollEndArea = false;
395+
396+
if (horizontal) {
397+
inAutoScrollBeginArea = pageX < containerLayout.pageX + this.props.autoscrollAreaSize;
398+
inAutoScrollEndArea = pageX > containerLayout.pageX + containerLayout.width - this.props.autoscrollAreaSize;
399+
} else {
400+
inAutoScrollBeginArea = pageY < containerLayout.pageY + this.props.autoscrollAreaSize;
401+
inAutoScrollEndArea = pageY > containerLayout.pageY + containerLayout.height - this.props.autoscrollAreaSize;
402+
}
352403

353-
if (!inAutoScrollUpArea &&
354-
!inAutoScrollDownArea &&
404+
if (!inAutoScrollBeginArea &&
405+
!inAutoScrollEndArea &&
355406
this._autoScrollInterval !== null
356407
) {
357408
this._stopAutoScroll();
@@ -362,33 +413,42 @@ export default class SortableList extends Component {
362413
return;
363414
}
364415

365-
if (inAutoScrollUpArea) {
416+
if (inAutoScrollBeginArea) {
366417
this._startAutoScroll({
367418
direction: -1,
368-
shouldScroll: () => this._contentOffset.y > 0,
419+
shouldScroll: () => this._contentOffset[horizontal ? 'x' : 'y'] > 0,
369420
getScrollStep: (stepIndex) => {
370421
const nextStep = this._getScrollStep(stepIndex);
422+
const contentOffset = this._contentOffset[horizontal ? 'x' : 'y'];
371423

372-
return this._contentOffset.y - nextStep < 0
373-
? this._contentOffset.y
374-
: nextStep;
424+
return contentOffset - nextStep < 0 ? contentOffset : nextStep;
375425
},
376426
});
377-
} else if (inAutoScrollDownArea) {
427+
} else if (inAutoScrollEndArea) {
378428
this._startAutoScroll({
379429
direction: 1,
380430
shouldScroll: () => {
381-
const {contentHeight, containerLayout} = this.state;
431+
const {contentHeight, contentWidth, containerLayout} = this.state;
382432

383-
return this._contentOffset.y < contentHeight - containerLayout.height;
433+
if (horizontal) {
434+
return this._contentOffset.x < contentWidth - containerLayout.width
435+
} else {
436+
return this._contentOffset.y < contentHeight - containerLayout.height;
437+
}
384438
},
385439
getScrollStep: (stepIndex) => {
386440
const nextStep = this._getScrollStep(stepIndex);
387-
const {contentHeight, containerLayout} = this.state;
388-
389-
return this._contentOffset.y + nextStep > contentHeight - containerLayout.height
390-
? contentHeight - containerLayout.height - this._contentOffset.y
391-
: nextStep;
441+
const {contentHeight, contentWidth, containerLayout} = this.state;
442+
443+
if (horizontal) {
444+
return this._contentOffset.x + nextStep > contentWidth - containerLayout.width
445+
? contentWidth - containerLayout.width - this._contentOffset.x
446+
: nextStep;
447+
} else {
448+
return this._contentOffset.y + nextStep > contentHeight - containerLayout.height
449+
? contentHeight - containerLayout.height - this._contentOffset.y
450+
: nextStep;
451+
}
392452
},
393453
});
394454
}
@@ -404,13 +464,17 @@ export default class SortableList extends Component {
404464
}
405465

406466
const {activeRowKey} = this.state;
467+
const {horizontal} = this.props;
407468
let counter = 0;
408469

409470
this._autoScrollInterval = setInterval(() => {
410471
if (shouldScroll()) {
411-
const dy = direction * getScrollStep(counter++);
412-
this.scrollBy({dy});
413-
this._rows[activeRowKey].moveBy({dy});
472+
const movement = {
473+
[horizontal ? 'dx' : 'dy']: direction * getScrollStep(counter++),
474+
};
475+
476+
this.scrollBy(movement);
477+
this._rows[activeRowKey].moveBy(movement);
414478
} else {
415479
this._stopAutoScroll();
416480
}
@@ -462,11 +526,14 @@ export default class SortableList extends Component {
462526
};
463527

464528
_onMoveRow = (e, gestureState, location) => {
529+
const prevMovingRowX = this._activeRowLocation.x;
465530
const prevMovingRowY = this._activeRowLocation.y;
466531
const prevMovingDirection = this._movingDirection;
467532

468533
this._activeRowLocation = location;
469-
this._movingDirection = prevMovingRowY < this._activeRowLocation.y;
534+
this._movingDirection = this.props.horizontal
535+
? prevMovingRowX < this._activeRowLocation.x
536+
: prevMovingRowY < this._activeRowLocation.y;
470537

471538
this._movingDirectionChanged = prevMovingDirection !== this._movingDirection;
472539
this._setOrderOnMove();

0 commit comments

Comments
 (0)