@gm-pc/sortable 1.24.7 → 1.24.9-beat.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/components/sortable.tsx +69 -69
- package/src/components/sortable_base.tsx +127 -127
- package/src/components/sortable_group.tsx +63 -63
- package/src/components/types.ts +50 -50
- package/src/index.ts +10 -10
- package/src/js.stories.tsx +177 -177
- package/src/sortable.stories.tsx +185 -185
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gm-pc/sortable",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.9-beat.2",
|
|
4
4
|
"description": "观麦科技拖拽组件",
|
|
5
5
|
"author": "liyatang <liyatang@qq.com>",
|
|
6
6
|
"homepage": "https://github.com/gmfe/gm-pc#readme",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@gm-common/tool": "^2.10.0",
|
|
30
|
-
"@gm-pc/react": "^1.24.
|
|
30
|
+
"@gm-pc/react": "^1.24.9-beat.2",
|
|
31
31
|
"lodash": "^4.17.19",
|
|
32
32
|
"sortablejs": "^1.12.0"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "307cc3b7b644f03b6591a9ead7408dbb43f37359",
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/sortablejs": "^1.13.0"
|
|
37
37
|
}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
2
|
-
import classNames from 'classnames'
|
|
3
|
-
import { devWarnForHook } from '@gm-common/tool'
|
|
4
|
-
import { Value, SortableDataItem, SortableProps } from './types'
|
|
5
|
-
import SortableBase from './sortable_base'
|
|
6
|
-
import { Options } from 'sortablejs'
|
|
7
|
-
|
|
8
|
-
const Sortable = ({
|
|
9
|
-
data,
|
|
10
|
-
onChange,
|
|
11
|
-
groupValues,
|
|
12
|
-
renderItem = (item: SortableDataItem) => item.text,
|
|
13
|
-
itemProps = {},
|
|
14
|
-
tag,
|
|
15
|
-
options = {},
|
|
16
|
-
...rest
|
|
17
|
-
}: SortableProps) => {
|
|
18
|
-
devWarnForHook(() => {
|
|
19
|
-
if (groupValues && !options.group) {
|
|
20
|
-
console.warn('groupValues need options.group')
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
let filterData = data
|
|
25
|
-
if (groupValues) {
|
|
26
|
-
filterData = data.filter((value) => groupValues.includes(value.value))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const handleChange = (values: string[]) => {
|
|
30
|
-
// 因为 SortableJS 默认吐出来都是 string,所以需要反编译拿回原来的类型
|
|
31
|
-
const newValues: Value[] = values.map((value) => JSON.parse(value))
|
|
32
|
-
const newValuesMap = new Map<string, SortableDataItem>()
|
|
33
|
-
data.forEach((value) => {
|
|
34
|
-
newValuesMap.set(value.value, value)
|
|
35
|
-
})
|
|
36
|
-
const newData: SortableDataItem[] = newValues.map((value) => newValuesMap.get(value)!)
|
|
37
|
-
onChange(newData)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const items = useMemo(
|
|
41
|
-
() =>
|
|
42
|
-
filterData.map((value, index) => (
|
|
43
|
-
<div
|
|
44
|
-
key={value.value as any}
|
|
45
|
-
data-id={JSON.stringify(value.value)}
|
|
46
|
-
{...itemProps}
|
|
47
|
-
className={classNames(
|
|
48
|
-
{ 'gm-cursor-grab': !options.handle },
|
|
49
|
-
itemProps.className
|
|
50
|
-
)}
|
|
51
|
-
>
|
|
52
|
-
{renderItem(value, index)}
|
|
53
|
-
</div>
|
|
54
|
-
)),
|
|
55
|
-
[filterData, renderItem, itemProps, options.handle]
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<SortableBase
|
|
60
|
-
{...rest}
|
|
61
|
-
tag={tag}
|
|
62
|
-
options={{ animation: 150, ...options }}
|
|
63
|
-
onChange={handleChange}
|
|
64
|
-
>
|
|
65
|
-
{items}
|
|
66
|
-
</SortableBase>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
export default Sortable
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { devWarnForHook } from '@gm-common/tool'
|
|
4
|
+
import { Value, SortableDataItem, SortableProps } from './types'
|
|
5
|
+
import SortableBase from './sortable_base'
|
|
6
|
+
import { Options } from 'sortablejs'
|
|
7
|
+
|
|
8
|
+
const Sortable = ({
|
|
9
|
+
data,
|
|
10
|
+
onChange,
|
|
11
|
+
groupValues,
|
|
12
|
+
renderItem = (item: SortableDataItem) => item.text,
|
|
13
|
+
itemProps = {},
|
|
14
|
+
tag,
|
|
15
|
+
options = {},
|
|
16
|
+
...rest
|
|
17
|
+
}: SortableProps) => {
|
|
18
|
+
devWarnForHook(() => {
|
|
19
|
+
if (groupValues && !options.group) {
|
|
20
|
+
console.warn('groupValues need options.group')
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
let filterData = data
|
|
25
|
+
if (groupValues) {
|
|
26
|
+
filterData = data.filter((value) => groupValues.includes(value.value))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleChange = (values: string[]) => {
|
|
30
|
+
// 因为 SortableJS 默认吐出来都是 string,所以需要反编译拿回原来的类型
|
|
31
|
+
const newValues: Value[] = values.map((value) => JSON.parse(value))
|
|
32
|
+
const newValuesMap = new Map<string, SortableDataItem>()
|
|
33
|
+
data.forEach((value) => {
|
|
34
|
+
newValuesMap.set(value.value, value)
|
|
35
|
+
})
|
|
36
|
+
const newData: SortableDataItem[] = newValues.map((value) => newValuesMap.get(value)!)
|
|
37
|
+
onChange(newData)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const items = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
filterData.map((value, index) => (
|
|
43
|
+
<div
|
|
44
|
+
key={value.value as any}
|
|
45
|
+
data-id={JSON.stringify(value.value)}
|
|
46
|
+
{...itemProps}
|
|
47
|
+
className={classNames(
|
|
48
|
+
{ 'gm-cursor-grab': !options.handle },
|
|
49
|
+
itemProps.className
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{renderItem(value, index)}
|
|
53
|
+
</div>
|
|
54
|
+
)),
|
|
55
|
+
[filterData, renderItem, itemProps, options.handle]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<SortableBase
|
|
60
|
+
{...rest}
|
|
61
|
+
tag={tag}
|
|
62
|
+
options={{ animation: 150, ...options }}
|
|
63
|
+
onChange={handleChange}
|
|
64
|
+
>
|
|
65
|
+
{items}
|
|
66
|
+
</SortableBase>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
export default Sortable
|
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
import React, { Component } from 'react'
|
|
2
|
-
import Sortable, { Options, SortableEvent, MoveEvent } from 'sortablejs'
|
|
3
|
-
import { SortableBaseProps } from './types'
|
|
4
|
-
|
|
5
|
-
type SortableEventType = Extract<
|
|
6
|
-
keyof Options,
|
|
7
|
-
| 'onChoose'
|
|
8
|
-
| 'onStart'
|
|
9
|
-
| 'onEnd'
|
|
10
|
-
| 'onAdd'
|
|
11
|
-
| 'onUpdate'
|
|
12
|
-
| 'onSort'
|
|
13
|
-
| 'onRemove'
|
|
14
|
-
| 'onFilter'
|
|
15
|
-
| 'onMove'
|
|
16
|
-
| 'onClone'
|
|
17
|
-
>
|
|
18
|
-
|
|
19
|
-
const SORTABLE_EVENTS: SortableEventType[] = [
|
|
20
|
-
'onChoose',
|
|
21
|
-
'onStart',
|
|
22
|
-
'onEnd',
|
|
23
|
-
'onAdd',
|
|
24
|
-
'onUpdate',
|
|
25
|
-
'onSort',
|
|
26
|
-
'onRemove',
|
|
27
|
-
'onFilter',
|
|
28
|
-
'onMove',
|
|
29
|
-
'onClone',
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
interface Store {
|
|
33
|
-
nextSibling: Element | null
|
|
34
|
-
activeComponent: SortableBase | null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const store: Store = { nextSibling: null, activeComponent: null }
|
|
38
|
-
|
|
39
|
-
class SortableBase extends Component<SortableBaseProps> {
|
|
40
|
-
static defaultProps = {
|
|
41
|
-
options: {},
|
|
42
|
-
tag: 'div',
|
|
43
|
-
style: {},
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private _node: HTMLElement | undefined
|
|
47
|
-
private _sortable: Sortable | undefined
|
|
48
|
-
|
|
49
|
-
componentDidMount() {
|
|
50
|
-
const option: Options = { ...this.props.options, disabled: this.props.disabled }
|
|
51
|
-
SORTABLE_EVENTS.forEach((name) => {
|
|
52
|
-
const eventHandler = option[name] as (
|
|
53
|
-
event: SortableEvent | MoveEvent,
|
|
54
|
-
originalEvent?: Event
|
|
55
|
-
) => void | boolean | -1 | 1
|
|
56
|
-
;(option[name] as (
|
|
57
|
-
event: SortableEvent | MoveEvent,
|
|
58
|
-
originalEvent?: Event
|
|
59
|
-
) => void | boolean | -1 | 1) = (event, originalEvent) => {
|
|
60
|
-
const _evt = event as SortableEvent
|
|
61
|
-
if (name === 'onChoose') {
|
|
62
|
-
store.nextSibling = _evt.item.nextElementSibling
|
|
63
|
-
store.activeComponent = this
|
|
64
|
-
} else if ((name === 'onAdd' || name === 'onUpdate') && this.props.onChange) {
|
|
65
|
-
const items = this._sortable?.toArray()
|
|
66
|
-
const remote = store.activeComponent
|
|
67
|
-
const remoteItems = remote?._sortable?.toArray()
|
|
68
|
-
const referenceNode =
|
|
69
|
-
store.nextSibling && store.nextSibling.parentNode !== null
|
|
70
|
-
? store.nextSibling
|
|
71
|
-
: null
|
|
72
|
-
_evt.from.insertBefore(_evt.item, referenceNode)
|
|
73
|
-
if (remote !== this) {
|
|
74
|
-
const remoteOptions = remote?.props.options ?? {}
|
|
75
|
-
if (
|
|
76
|
-
typeof remoteOptions.group === 'object' &&
|
|
77
|
-
remoteOptions.group.pull === 'clone'
|
|
78
|
-
) {
|
|
79
|
-
// remove the node with the same data-id
|
|
80
|
-
// eslint-disable-next-line
|
|
81
|
-
_evt.item.parentNode?.removeChild(_evt.item)
|
|
82
|
-
}
|
|
83
|
-
remote?.props.onChange &&
|
|
84
|
-
remote.props.onChange(remoteItems!, remote._sortable!, _evt)
|
|
85
|
-
}
|
|
86
|
-
this.props.onChange && this.props.onChange(items!, this._sortable!, _evt)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if ((event as MoveEvent).type === 'move') {
|
|
90
|
-
return eventHandler ? eventHandler(event, originalEvent) : true
|
|
91
|
-
}
|
|
92
|
-
setTimeout(() => {
|
|
93
|
-
eventHandler && eventHandler(event)
|
|
94
|
-
}, 0)
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
this._sortable = Sortable.create(this._node!, option) // 不可直接解构使用 create,原因未知
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// eslint-disable-next-line camelcase
|
|
101
|
-
UNSAFE_componentWillReceiveProps(nextProps: Readonly<SortableBaseProps>) {
|
|
102
|
-
if (nextProps.disabled !== this.props.disabled) {
|
|
103
|
-
// eslint-disable-next-line
|
|
104
|
-
this._sortable?.option('disabled', nextProps.disabled)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
shouldComponentUpdate(nextProps: Readonly<SortableBaseProps>): boolean {
|
|
109
|
-
// If onChange is null, it is an UnControlled component
|
|
110
|
-
// Don't let React re-render it by setting return to false
|
|
111
|
-
return !!nextProps.onChange
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
componentWillUnmount() {
|
|
115
|
-
if (this._sortable) {
|
|
116
|
-
this._sortable.destroy()
|
|
117
|
-
this._sortable = undefined
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
render() {
|
|
122
|
-
const { tag: Tag, options, onChange, disabled, ...rest } = this.props
|
|
123
|
-
return <Tag {...rest} ref={(ref: HTMLElement) => (this._node = ref)} />
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export default SortableBase
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
import Sortable, { Options, SortableEvent, MoveEvent } from 'sortablejs'
|
|
3
|
+
import { SortableBaseProps } from './types'
|
|
4
|
+
|
|
5
|
+
type SortableEventType = Extract<
|
|
6
|
+
keyof Options,
|
|
7
|
+
| 'onChoose'
|
|
8
|
+
| 'onStart'
|
|
9
|
+
| 'onEnd'
|
|
10
|
+
| 'onAdd'
|
|
11
|
+
| 'onUpdate'
|
|
12
|
+
| 'onSort'
|
|
13
|
+
| 'onRemove'
|
|
14
|
+
| 'onFilter'
|
|
15
|
+
| 'onMove'
|
|
16
|
+
| 'onClone'
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
const SORTABLE_EVENTS: SortableEventType[] = [
|
|
20
|
+
'onChoose',
|
|
21
|
+
'onStart',
|
|
22
|
+
'onEnd',
|
|
23
|
+
'onAdd',
|
|
24
|
+
'onUpdate',
|
|
25
|
+
'onSort',
|
|
26
|
+
'onRemove',
|
|
27
|
+
'onFilter',
|
|
28
|
+
'onMove',
|
|
29
|
+
'onClone',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
interface Store {
|
|
33
|
+
nextSibling: Element | null
|
|
34
|
+
activeComponent: SortableBase | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const store: Store = { nextSibling: null, activeComponent: null }
|
|
38
|
+
|
|
39
|
+
class SortableBase extends Component<SortableBaseProps> {
|
|
40
|
+
static defaultProps = {
|
|
41
|
+
options: {},
|
|
42
|
+
tag: 'div',
|
|
43
|
+
style: {},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private _node: HTMLElement | undefined
|
|
47
|
+
private _sortable: Sortable | undefined
|
|
48
|
+
|
|
49
|
+
componentDidMount() {
|
|
50
|
+
const option: Options = { ...this.props.options, disabled: this.props.disabled }
|
|
51
|
+
SORTABLE_EVENTS.forEach((name) => {
|
|
52
|
+
const eventHandler = option[name] as (
|
|
53
|
+
event: SortableEvent | MoveEvent,
|
|
54
|
+
originalEvent?: Event
|
|
55
|
+
) => void | boolean | -1 | 1
|
|
56
|
+
;(option[name] as (
|
|
57
|
+
event: SortableEvent | MoveEvent,
|
|
58
|
+
originalEvent?: Event
|
|
59
|
+
) => void | boolean | -1 | 1) = (event, originalEvent) => {
|
|
60
|
+
const _evt = event as SortableEvent
|
|
61
|
+
if (name === 'onChoose') {
|
|
62
|
+
store.nextSibling = _evt.item.nextElementSibling
|
|
63
|
+
store.activeComponent = this
|
|
64
|
+
} else if ((name === 'onAdd' || name === 'onUpdate') && this.props.onChange) {
|
|
65
|
+
const items = this._sortable?.toArray()
|
|
66
|
+
const remote = store.activeComponent
|
|
67
|
+
const remoteItems = remote?._sortable?.toArray()
|
|
68
|
+
const referenceNode =
|
|
69
|
+
store.nextSibling && store.nextSibling.parentNode !== null
|
|
70
|
+
? store.nextSibling
|
|
71
|
+
: null
|
|
72
|
+
_evt.from.insertBefore(_evt.item, referenceNode)
|
|
73
|
+
if (remote !== this) {
|
|
74
|
+
const remoteOptions = remote?.props.options ?? {}
|
|
75
|
+
if (
|
|
76
|
+
typeof remoteOptions.group === 'object' &&
|
|
77
|
+
remoteOptions.group.pull === 'clone'
|
|
78
|
+
) {
|
|
79
|
+
// remove the node with the same data-id
|
|
80
|
+
// eslint-disable-next-line
|
|
81
|
+
_evt.item.parentNode?.removeChild(_evt.item)
|
|
82
|
+
}
|
|
83
|
+
remote?.props.onChange &&
|
|
84
|
+
remote.props.onChange(remoteItems!, remote._sortable!, _evt)
|
|
85
|
+
}
|
|
86
|
+
this.props.onChange && this.props.onChange(items!, this._sortable!, _evt)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ((event as MoveEvent).type === 'move') {
|
|
90
|
+
return eventHandler ? eventHandler(event, originalEvent) : true
|
|
91
|
+
}
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
eventHandler && eventHandler(event)
|
|
94
|
+
}, 0)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
this._sortable = Sortable.create(this._node!, option) // 不可直接解构使用 create,原因未知
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line camelcase
|
|
101
|
+
UNSAFE_componentWillReceiveProps(nextProps: Readonly<SortableBaseProps>) {
|
|
102
|
+
if (nextProps.disabled !== this.props.disabled) {
|
|
103
|
+
// eslint-disable-next-line
|
|
104
|
+
this._sortable?.option('disabled', nextProps.disabled)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
shouldComponentUpdate(nextProps: Readonly<SortableBaseProps>): boolean {
|
|
109
|
+
// If onChange is null, it is an UnControlled component
|
|
110
|
+
// Don't let React re-render it by setting return to false
|
|
111
|
+
return !!nextProps.onChange
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
componentWillUnmount() {
|
|
115
|
+
if (this._sortable) {
|
|
116
|
+
this._sortable.destroy()
|
|
117
|
+
this._sortable = undefined
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
render() {
|
|
122
|
+
const { tag: Tag, options, onChange, disabled, ...rest } = this.props
|
|
123
|
+
return <Tag {...rest} ref={(ref: HTMLElement) => (this._node = ref)} />
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default SortableBase
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import React, { useMemo, useRef } from 'react'
|
|
2
|
-
import _ from 'lodash'
|
|
3
|
-
import { GroupSortableProps, SortableDataItem } from './types'
|
|
4
|
-
import Sortable from './sortable'
|
|
5
|
-
|
|
6
|
-
const GroupSortable = ({
|
|
7
|
-
data,
|
|
8
|
-
onChange,
|
|
9
|
-
renderItem,
|
|
10
|
-
itemProps,
|
|
11
|
-
tag,
|
|
12
|
-
options,
|
|
13
|
-
children,
|
|
14
|
-
}: GroupSortableProps) => {
|
|
15
|
-
/**
|
|
16
|
-
* 两个列表之间的拖动,中间变量
|
|
17
|
-
*/
|
|
18
|
-
const dataRef = useRef<SortableDataItem[][]>([])
|
|
19
|
-
const flatData = useMemo(() => _.flatten(data), [data])
|
|
20
|
-
const items = data.map((subData, index) => {
|
|
21
|
-
const handleChange = (newSubData: SortableDataItem[]): void => {
|
|
22
|
-
// 变化的才会触发 change,
|
|
23
|
-
// 单个列表内拖动只一次 change,此时 newSubData 长度和原先一样
|
|
24
|
-
// 如果列表之间拖动,当且仅当两次 change,此时 newSubData 长度和原先不一样
|
|
25
|
-
if (newSubData.length === data[index].length) {
|
|
26
|
-
const newData = data.slice()
|
|
27
|
-
newData[index] = newSubData
|
|
28
|
-
onChange(newData)
|
|
29
|
-
} else {
|
|
30
|
-
// 初次进入,会先修改被拖出的数组
|
|
31
|
-
if (dataRef.current.length === 0) {
|
|
32
|
-
// 第一次会先将 dataRef 填充,并将被拖出的那一项修改为 newSubData
|
|
33
|
-
dataRef.current = data.slice()
|
|
34
|
-
dataRef.current[index] = newSubData
|
|
35
|
-
} else {
|
|
36
|
-
// 第二次会拿到 dataRef,并将被拖入的那一项修改为 newSubData
|
|
37
|
-
dataRef.current[index] = newSubData
|
|
38
|
-
// 避免引用 slice
|
|
39
|
-
const newData = dataRef.current.slice()
|
|
40
|
-
// 还原
|
|
41
|
-
dataRef.current = []
|
|
42
|
-
onChange(newData)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<Sortable
|
|
49
|
-
key={_.uniqueId()}
|
|
50
|
-
data={flatData}
|
|
51
|
-
groupValues={subData.map((val) => val.value)}
|
|
52
|
-
onChange={handleChange}
|
|
53
|
-
renderItem={renderItem}
|
|
54
|
-
itemProps={itemProps}
|
|
55
|
-
tag={tag}
|
|
56
|
-
options={{ group: 'group', ...options }}
|
|
57
|
-
/>
|
|
58
|
-
)
|
|
59
|
-
})
|
|
60
|
-
return children(items)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export default GroupSortable
|
|
1
|
+
import React, { useMemo, useRef } from 'react'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { GroupSortableProps, SortableDataItem } from './types'
|
|
4
|
+
import Sortable from './sortable'
|
|
5
|
+
|
|
6
|
+
const GroupSortable = ({
|
|
7
|
+
data,
|
|
8
|
+
onChange,
|
|
9
|
+
renderItem,
|
|
10
|
+
itemProps,
|
|
11
|
+
tag,
|
|
12
|
+
options,
|
|
13
|
+
children,
|
|
14
|
+
}: GroupSortableProps) => {
|
|
15
|
+
/**
|
|
16
|
+
* 两个列表之间的拖动,中间变量
|
|
17
|
+
*/
|
|
18
|
+
const dataRef = useRef<SortableDataItem[][]>([])
|
|
19
|
+
const flatData = useMemo(() => _.flatten(data), [data])
|
|
20
|
+
const items = data.map((subData, index) => {
|
|
21
|
+
const handleChange = (newSubData: SortableDataItem[]): void => {
|
|
22
|
+
// 变化的才会触发 change,
|
|
23
|
+
// 单个列表内拖动只一次 change,此时 newSubData 长度和原先一样
|
|
24
|
+
// 如果列表之间拖动,当且仅当两次 change,此时 newSubData 长度和原先不一样
|
|
25
|
+
if (newSubData.length === data[index].length) {
|
|
26
|
+
const newData = data.slice()
|
|
27
|
+
newData[index] = newSubData
|
|
28
|
+
onChange(newData)
|
|
29
|
+
} else {
|
|
30
|
+
// 初次进入,会先修改被拖出的数组
|
|
31
|
+
if (dataRef.current.length === 0) {
|
|
32
|
+
// 第一次会先将 dataRef 填充,并将被拖出的那一项修改为 newSubData
|
|
33
|
+
dataRef.current = data.slice()
|
|
34
|
+
dataRef.current[index] = newSubData
|
|
35
|
+
} else {
|
|
36
|
+
// 第二次会拿到 dataRef,并将被拖入的那一项修改为 newSubData
|
|
37
|
+
dataRef.current[index] = newSubData
|
|
38
|
+
// 避免引用 slice
|
|
39
|
+
const newData = dataRef.current.slice()
|
|
40
|
+
// 还原
|
|
41
|
+
dataRef.current = []
|
|
42
|
+
onChange(newData)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Sortable
|
|
49
|
+
key={_.uniqueId()}
|
|
50
|
+
data={flatData}
|
|
51
|
+
groupValues={subData.map((val) => val.value)}
|
|
52
|
+
onChange={handleChange}
|
|
53
|
+
renderItem={renderItem}
|
|
54
|
+
itemProps={itemProps}
|
|
55
|
+
tag={tag}
|
|
56
|
+
options={{ group: 'group', ...options }}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
return children(items)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default GroupSortable
|
package/src/components/types.ts
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import Sortable, { Options, SortableEvent } from 'sortablejs'
|
|
2
|
-
import { ElementType, HTMLAttributes, ReactElement, ReactNode } from 'react'
|
|
3
|
-
|
|
4
|
-
type Value = any
|
|
5
|
-
|
|
6
|
-
interface SortableBaseProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
7
|
-
options: Options
|
|
8
|
-
onChange?(remoteItems: string[], sortable: Sortable, event: SortableEvent): void
|
|
9
|
-
tag: ElementType
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface SortableDataItem {
|
|
14
|
-
value: Value
|
|
15
|
-
text: string
|
|
16
|
-
[key: string]: any
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface SortableCommonProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
20
|
-
/**
|
|
21
|
-
* options.group 有值的时候要传。
|
|
22
|
-
* 此时 data 是 group 集合数据,groupValues 是当前组件的数据
|
|
23
|
-
*/
|
|
24
|
-
groupValues?: Value[]
|
|
25
|
-
renderItem?(value: SortableDataItem, index: number): ReactNode
|
|
26
|
-
itemProps?: HTMLAttributes<HTMLDivElement>
|
|
27
|
-
tag?: ElementType
|
|
28
|
-
options?: Options
|
|
29
|
-
disabled?: boolean
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface SortableProps extends SortableCommonProps {
|
|
33
|
-
data: SortableDataItem[]
|
|
34
|
-
onChange(data: SortableDataItem[]): void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface GroupSortableProps extends SortableCommonProps {
|
|
38
|
-
/* 二维数组 */
|
|
39
|
-
data: SortableDataItem[][]
|
|
40
|
-
onChange(data: SortableDataItem[][]): void
|
|
41
|
-
children: (items: ReactElement<SortableProps>[]) => ReactElement
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type {
|
|
45
|
-
Value,
|
|
46
|
-
SortableBaseProps,
|
|
47
|
-
SortableDataItem,
|
|
48
|
-
SortableProps,
|
|
49
|
-
GroupSortableProps,
|
|
50
|
-
}
|
|
1
|
+
import Sortable, { Options, SortableEvent } from 'sortablejs'
|
|
2
|
+
import { ElementType, HTMLAttributes, ReactElement, ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
type Value = any
|
|
5
|
+
|
|
6
|
+
interface SortableBaseProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
7
|
+
options: Options
|
|
8
|
+
onChange?(remoteItems: string[], sortable: Sortable, event: SortableEvent): void
|
|
9
|
+
tag: ElementType
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SortableDataItem {
|
|
14
|
+
value: Value
|
|
15
|
+
text: string
|
|
16
|
+
[key: string]: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SortableCommonProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
20
|
+
/**
|
|
21
|
+
* options.group 有值的时候要传。
|
|
22
|
+
* 此时 data 是 group 集合数据,groupValues 是当前组件的数据
|
|
23
|
+
*/
|
|
24
|
+
groupValues?: Value[]
|
|
25
|
+
renderItem?(value: SortableDataItem, index: number): ReactNode
|
|
26
|
+
itemProps?: HTMLAttributes<HTMLDivElement>
|
|
27
|
+
tag?: ElementType
|
|
28
|
+
options?: Options
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface SortableProps extends SortableCommonProps {
|
|
33
|
+
data: SortableDataItem[]
|
|
34
|
+
onChange(data: SortableDataItem[]): void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface GroupSortableProps extends SortableCommonProps {
|
|
38
|
+
/* 二维数组 */
|
|
39
|
+
data: SortableDataItem[][]
|
|
40
|
+
onChange(data: SortableDataItem[][]): void
|
|
41
|
+
children: (items: ReactElement<SortableProps>[]) => ReactElement
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
Value,
|
|
46
|
+
SortableBaseProps,
|
|
47
|
+
SortableDataItem,
|
|
48
|
+
SortableProps,
|
|
49
|
+
GroupSortableProps,
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { default as Sortable } from './components/sortable'
|
|
2
|
-
export { default as SortableBase } from './components/sortable_base'
|
|
3
|
-
export { default as GroupSortable } from './components/sortable_group'
|
|
4
|
-
|
|
5
|
-
export type {
|
|
6
|
-
SortableProps,
|
|
7
|
-
SortableBaseProps,
|
|
8
|
-
SortableDataItem,
|
|
9
|
-
GroupSortableProps,
|
|
10
|
-
} from './components/types'
|
|
1
|
+
export { default as Sortable } from './components/sortable'
|
|
2
|
+
export { default as SortableBase } from './components/sortable_base'
|
|
3
|
+
export { default as GroupSortable } from './components/sortable_group'
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
SortableProps,
|
|
7
|
+
SortableBaseProps,
|
|
8
|
+
SortableDataItem,
|
|
9
|
+
GroupSortableProps,
|
|
10
|
+
} from './components/types'
|