Skip to content

Commit 90e578b

Browse files
committed
feat: simple implemention of sortable
1 parent 4dcc4d2 commit 90e578b

File tree

3 files changed

+551
-8
lines changed

3 files changed

+551
-8
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"build": "rollup -c",
1111
"publish": "rollup -c & npm publish",
1212
"coverage": "jest --coverage",
13-
"test": "jest"
13+
"test": "jest",
14+
"precommit": "lint-staged"
1415
},
1516
"repository": "git@github.com:teabyii/react-sortable-list.git",
1617
"author": "teabyii <teabyii@gmail.com>",
@@ -20,7 +21,9 @@
2021
"babel-plugin-external-helpers": "^6.22.0",
2122
"babel-preset-env": "^1.6.1",
2223
"babel-preset-react": "^6.24.1",
24+
"husky": "^0.14.3",
2325
"jest": "^22.2.1",
26+
"lint-staged": "^6.1.0",
2427
"rollup": "^0.55.3",
2528
"rollup-plugin-babel": "^3.0.3",
2629
"rollup-plugin-node-resolve": "^3.0.2",

src/index.js

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import React, { Component } from 'react'
2+
import PropTypes from 'prop-types'
3+
4+
/**
5+
* 可拖动排序的单元
6+
*
7+
* @export
8+
* @class Item
9+
* @extends {Component}
10+
*/
11+
export class Item extends Component {}
12+
13+
/**
14+
* SortableList Component
15+
*
16+
* @class SortableList
17+
* @extends {Component}
18+
*/
19+
export class SortableList extends Component {
20+
/**
21+
* Creates an instance of SortableList.
22+
* @memberof SortableList
23+
*/
24+
constructor (props, context) {
25+
super(props, context)
26+
this.items = []
27+
this.count = 0 // 重新排序时的处理顺序
28+
this.data = {
29+
dragging: false,
30+
current: undefined,
31+
list: []
32+
}
33+
34+
this.wrap(props.children)
35+
this.state = {
36+
list: this.getListByItems()
37+
}
38+
}
39+
40+
/**
41+
* 在子元素更新的情况下,调整 items 数据和索引列表 list
42+
*
43+
* @param {object} nextProps
44+
* @memberof SortableList
45+
*/
46+
componentWillReceiveProps (nextProps) {
47+
if (this.props.children !== nextProps.children) {
48+
// 重置子元素,重新获取
49+
this.items = []
50+
this.wrap(nextProps.children)
51+
this.setState({
52+
list: this.getListByItems()
53+
})
54+
}
55+
}
56+
57+
/**
58+
* 通过可排序的子元素生成对应的索引列表
59+
*
60+
* @returns
61+
* @memberof SortableList
62+
*/
63+
getListByItems () {
64+
return new Array(this.items.length).fill(1).map((_, i) => i)
65+
}
66+
67+
/**
68+
* 开始拖拽,主要获取当前拖动的元素索引
69+
*
70+
* @param {SyntheticEvent} event
71+
* @memberof SortableList
72+
*/
73+
handleDragStart (event) {
74+
if (!this.data.dragging) {
75+
event.dataTransfer.setData('text/html', event.target)
76+
this.data.dragging = true
77+
this.data.current = event.target.getAttribute('data-index')
78+
this.data.list = [...this.state.list]
79+
}
80+
}
81+
82+
/**
83+
* 拖拽时移动到其他元素,调用对应的方法重新排序
84+
*
85+
* @param {SyntheticEvent} event
86+
* @memberof SortableList
87+
*/
88+
handleDragEnter (event) {
89+
const { dragging, current } = this.data
90+
const index = event.currentTarget.getAttribute('data-index')
91+
if (dragging && current !== index) {
92+
this.sortPosition(Number(current), Number(index))
93+
}
94+
}
95+
96+
/**
97+
* 处理拖拽时的移动
98+
*
99+
* @param {SyntheticEvent} event
100+
* @memberof SortableList
101+
*/
102+
handleDragOver (event) {
103+
event.preventDefault()
104+
event.dataTransfer.effectAllowed = 'move'
105+
}
106+
107+
/**
108+
* 处理拖拽后放置元素
109+
* 调用 props.onSort 方法,重置拖拽状态数据
110+
*
111+
* @param {SyntheticEvent} event
112+
* @memberof SortableList
113+
*/
114+
handleDrop (event) {
115+
event.persist()
116+
event.dropEffect = 'move'
117+
event.dataTransfer.setData('text/html', null)
118+
119+
// 顺序不变时不调用 onSort
120+
if (
121+
typeof this.props.onSort === 'function' &&
122+
this.data.list.join('-') !== this.state.list.join('-') // 简单的数组 diff
123+
) {
124+
this.props.onSort.call(this, event.target, this.state.list)
125+
}
126+
127+
// 重置拖拽数据
128+
this.data = {
129+
dragging: false,
130+
current: undefined,
131+
list: []
132+
}
133+
}
134+
135+
/**
136+
* 给列表项添加 div 外层元素,并绑定相关属性和事件,记录所有的排序元素
137+
*
138+
* @param {ReactElement[]} children
139+
* @returns
140+
* @memberof SortableList
141+
*/
142+
wrap (node) {
143+
if (!node) {
144+
return
145+
}
146+
147+
if (Array.isArray(node)) {
148+
node.forEach(child => this.wrap(child))
149+
} else if (node.type === Item) {
150+
const index = this.items.length
151+
const item = (
152+
<div
153+
{...node.props}
154+
key={index}
155+
data-index={`${index}`}
156+
draggable
157+
onDragStart={this.handleDragStart.bind(this)}
158+
onDragEnter={this.handleDragEnter.bind(this)}
159+
onDragOver={this.handleDragOver.bind(this)}
160+
onDrop={this.handleDrop.bind(this)}
161+
>
162+
{node.props.children}
163+
</div>
164+
)
165+
this.items.push(item)
166+
} else if (node.props && node.props.children) {
167+
this.wrap(node.props.children)
168+
}
169+
}
170+
171+
/**
172+
* 根据 state.list 中保存的索引顺序重新整理子元素
173+
*
174+
* @param {ReactElement[]} children
175+
* @returns
176+
* @memberof SortableList
177+
*/
178+
sort (node) {
179+
if (!node) {
180+
return null
181+
}
182+
183+
if (Array.isArray(node)) {
184+
return node.map(child => this.sort(child))
185+
} else if (node.type === Item) {
186+
const index = this.state.list[this.count++]
187+
const item = this.items[index]
188+
return item
189+
} else if (node.props && node.props.children) {
190+
const children = Array.isArray(node.props.children) ? node.props.children : [node.props.children]
191+
return React.cloneElement(node, node.props, ...this.sort(children))
192+
} else {
193+
return node
194+
}
195+
}
196+
197+
/**
198+
* 根据传入的元素索引调整位置
199+
*
200+
* @param {number} x
201+
* @param {number} y
202+
*
203+
* @memberof SortableList
204+
*/
205+
sortPosition (x, y) {
206+
const { list } = this.state
207+
const xIndex = list.indexOf(x)
208+
list.splice(xIndex, 1) // 先移除 x
209+
const yIndex = list.indexOf(y)
210+
211+
if (xIndex <= yIndex) {
212+
list.splice(yIndex + 1, 0, x)
213+
} else {
214+
list.splice(yIndex, 0, x)
215+
}
216+
217+
this.setState({ list })
218+
}
219+
220+
/**
221+
* 渲染组件元素
222+
*
223+
* @memberOf @SortableList
224+
*/
225+
render () {
226+
const attrs = Object.assign({}, this.props)
227+
delete attrs.children
228+
229+
const items = this.sort(this.props.children)
230+
this.count = 0 // 重置计数
231+
delete attrs.onSort
232+
233+
return (
234+
<div {...attrs}>
235+
{items}
236+
</div>
237+
)
238+
}
239+
}
240+
241+
SortableList.propTypes = {
242+
onSort: PropTypes.func
243+
}

0 commit comments

Comments
 (0)