react-reorder-list 0.1.3 → 0.2.0
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/README.md +5 -5
- package/dist/animation.d.ts +7 -0
- package/dist/animation.js +49 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +37 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ import React from 'react'
|
|
|
31
31
|
import ReorderList from 'react-reorder-list'
|
|
32
32
|
|
|
33
33
|
export default function App() {
|
|
34
|
-
return <ReorderList
|
|
34
|
+
return <ReorderList>
|
|
35
35
|
{[0, 1, 2, 3, 4].map(i => {
|
|
36
36
|
{/* Having a unique key is important */}
|
|
37
37
|
return <div key={i}>{i}</div>
|
|
@@ -46,7 +46,7 @@ import React from 'react'
|
|
|
46
46
|
import ReorderList, { ReorderIcon } from 'react-reorder-list'
|
|
47
47
|
|
|
48
48
|
export default function App() {
|
|
49
|
-
return <ReorderList>
|
|
49
|
+
return <ReorderList useOnlyIconToDrag={true}>
|
|
50
50
|
{[0, 1, 2, 3, 4].map(i => {
|
|
51
51
|
return <div key={i}>
|
|
52
52
|
<ReorderIcon /> {/* Default icon */}
|
|
@@ -68,9 +68,8 @@ export default function App() {
|
|
|
68
68
|
return <ReorderList>
|
|
69
69
|
{[0, 1, 2].map(i => {
|
|
70
70
|
return <div key={i}>
|
|
71
|
-
<ReorderIcon />
|
|
72
71
|
<span>{'Parent' + i}</span>
|
|
73
|
-
<ReorderList>
|
|
72
|
+
<ReorderList useOnlyIconToDrag={true}>
|
|
74
73
|
{[0, 1, 2].map(j => {
|
|
75
74
|
return <div key={j} style={{ paddingLeft: '16px' }}>
|
|
76
75
|
<ReorderIcon />
|
|
@@ -87,8 +86,9 @@ export default function App() {
|
|
|
87
86
|
Here is the full API for the `<ReorderList>` component, these properties can be set on an instance of ReorderList:
|
|
88
87
|
| Parameter | Type | Required | Default | Description |
|
|
89
88
|
| - | - | - | - | - |
|
|
90
|
-
| `useOnlyIconToDrag` | `Boolean` | No |
|
|
89
|
+
| `useOnlyIconToDrag` | `Boolean` | No | false | See [usage with ReorderIcon](#usage-with-reordericon) |
|
|
91
90
|
| `selectedItemOpacity` | `Number (0 to 1)` | No | 0.5 | This determines the opacity of the item being dragged, until released. |
|
|
91
|
+
| `animationDuration` | `Number` | No | 400 | The duration of swapping animation between items. If set to 0, animation will be disabled. |
|
|
92
92
|
| `onPositionChange` | [`PositionChangeHandler`](#positionchangehandler) | No | - | Function to be executed on item position change. |
|
|
93
93
|
| `disable` | `Boolean` | No | false | When set to true, `ReorderList` will work as a plain `div` with no functionality. |
|
|
94
94
|
| `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<ReorderList>` component. |
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
type AnimationProps = {
|
|
3
|
+
duration: number;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
export default function Animation({ duration, children }: AnimationProps): string | number | boolean | Iterable<React.ReactNode> | React.JSX.Element | null | undefined;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState, useLayoutEffect, Children, isValidElement, useEffect, useRef } from "react";
|
|
2
|
+
function usePrevious(value) {
|
|
3
|
+
const prevChildrenRef = useRef();
|
|
4
|
+
useEffect(() => { prevChildrenRef.current = value; }, [value]);
|
|
5
|
+
return prevChildrenRef.current;
|
|
6
|
+
}
|
|
7
|
+
;
|
|
8
|
+
function calculateBoundingBoxes(children) {
|
|
9
|
+
const boundingBoxes = {};
|
|
10
|
+
Children.forEach(children, (child, i) => {
|
|
11
|
+
if (isValidElement(child))
|
|
12
|
+
boundingBoxes[child.key || i] = child.ref.current.getBoundingClientRect();
|
|
13
|
+
});
|
|
14
|
+
return boundingBoxes;
|
|
15
|
+
}
|
|
16
|
+
;
|
|
17
|
+
function Animate({ duration, children }) {
|
|
18
|
+
const [boundingBox, setBoundingBox] = useState({});
|
|
19
|
+
const prevBoundingBox = usePrevious(boundingBox);
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
const newBoundingBox = calculateBoundingBoxes(children);
|
|
22
|
+
setBoundingBox(newBoundingBox);
|
|
23
|
+
}, [children]);
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
if (prevBoundingBox && Object.keys(prevBoundingBox).length)
|
|
26
|
+
Children.forEach(children, (child, i) => {
|
|
27
|
+
if (!isValidElement(child))
|
|
28
|
+
return;
|
|
29
|
+
const domNode = child.ref.current;
|
|
30
|
+
const { left: prevLeft, top: prevTop } = prevBoundingBox[child.key || i];
|
|
31
|
+
const { left, top } = boundingBox[child.key || i];
|
|
32
|
+
const changeInX = prevLeft - left, changeInY = prevTop - top;
|
|
33
|
+
if (changeInX || changeInY)
|
|
34
|
+
requestAnimationFrame(() => {
|
|
35
|
+
domNode.style.transform = `translate(${changeInX}px, ${changeInY}px)`;
|
|
36
|
+
domNode.style.transition = "none";
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
domNode.style.transform = "";
|
|
39
|
+
domNode.style.transition = `transform ${duration}ms`;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}, [boundingBox]);
|
|
44
|
+
return children;
|
|
45
|
+
}
|
|
46
|
+
;
|
|
47
|
+
export default function Animation({ duration, children }) {
|
|
48
|
+
return duration ? React.createElement(Animate, { duration: duration }, children) : children;
|
|
49
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,11 +9,12 @@ export type PositionChangeHandler = (params?: {
|
|
|
9
9
|
export type ReorderListProps = {
|
|
10
10
|
useOnlyIconToDrag?: boolean;
|
|
11
11
|
selectedItemOpacity?: number;
|
|
12
|
+
animationDuration?: number;
|
|
12
13
|
onPositionChange?: PositionChangeHandler;
|
|
13
14
|
disable?: boolean;
|
|
14
15
|
props?: Props;
|
|
15
16
|
children?: ReactNode;
|
|
16
17
|
};
|
|
17
18
|
export type { IconProps } from './icons.js';
|
|
18
|
-
export default function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, onPositionChange, disable, props, children }: ReorderListProps): React.JSX.Element;
|
|
19
|
+
export default function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, animationDuration, onPositionChange, disable, props, children }: ReorderListProps): React.JSX.Element;
|
|
19
20
|
export declare const ReorderIcon: ({ children, style, ...props }: Props) => React.JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -9,56 +9,64 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import React, { Children, cloneElement, isValidElement, useMemo, useState } from 'react';
|
|
12
|
+
import React, { Children, cloneElement, createRef, forwardRef, isValidElement, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { PiDotsSixVerticalBold } from './icons.js';
|
|
14
|
-
|
|
14
|
+
import Animation from './animation.js';
|
|
15
|
+
const ReorderItem = forwardRef(({ useOnlyIconToDrag, draggable, style, onDragStart, onDragEnter, onDragEnd, onPointerEnter, onPointerLeave, children }, ref) => {
|
|
16
|
+
const props = draggable ? { draggable, onDragStart, onDragEnter, onDragEnd } : {};
|
|
17
|
+
const recursiveClone = (children) => Children.map(children, child => {
|
|
18
|
+
if (!isValidElement(child))
|
|
19
|
+
return child;
|
|
20
|
+
const childProps = {};
|
|
21
|
+
if (useOnlyIconToDrag && child.type.name === 'ReorderIcon') {
|
|
22
|
+
childProps.onPointerEnter = onPointerEnter;
|
|
23
|
+
childProps.onPointerLeave = onPointerLeave;
|
|
24
|
+
}
|
|
25
|
+
return cloneElement(child, Object.assign({ children: recursiveClone(child.props.children) }, childProps));
|
|
26
|
+
});
|
|
27
|
+
const recursiveChildren = useMemo(() => recursiveClone(children), [children]);
|
|
28
|
+
return React.createElement("div", Object.assign({ ref: ref, style: style }, props), recursiveChildren);
|
|
29
|
+
});
|
|
30
|
+
export default function ReorderList({ useOnlyIconToDrag = false, selectedItemOpacity = 0.5, animationDuration = 400, onPositionChange, disable = false, props, children }) {
|
|
31
|
+
const ref = useRef();
|
|
32
|
+
const [draggable, setDraggable] = useState(!useOnlyIconToDrag);
|
|
15
33
|
const [start, setStart] = useState(-1);
|
|
16
34
|
const [selected, setSelected] = useState(-1);
|
|
17
35
|
const [items, setItems] = useState(children);
|
|
18
|
-
const [temp, setTemp] = useState(
|
|
19
|
-
|
|
36
|
+
const [temp, setTemp] = useState({});
|
|
37
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
38
|
+
return React.createElement("div", Object.assign({ ref: ref }, props), disable ? children : React.createElement(Animation, { duration: +draggable && animationDuration }, Children.map(items, (child, i) => {
|
|
20
39
|
if (!isValidElement(child))
|
|
21
40
|
return child;
|
|
22
|
-
return React.createElement(ReorderItem, { key: child.key || i, opacity: selected === i ? selectedItemOpacity : 1
|
|
41
|
+
return React.createElement(ReorderItem, { key: child.key || i, ref: createRef(), useOnlyIconToDrag: useOnlyIconToDrag, draggable: draggable, style: { opacity: selected === i ? selectedItemOpacity : 1 }, onDragStart: event => {
|
|
23
42
|
event.stopPropagation();
|
|
24
43
|
setStart(i);
|
|
25
44
|
setSelected(i);
|
|
26
|
-
setTemp(items);
|
|
45
|
+
setTemp({ items, rect: ref.current.children[i].getBoundingClientRect() });
|
|
27
46
|
}, onDragEnter: event => {
|
|
28
47
|
event.stopPropagation();
|
|
29
|
-
if (start === -1)
|
|
48
|
+
if (start === -1 || selected === i || isAnimating)
|
|
49
|
+
return;
|
|
50
|
+
const { width: startWidth, height: startHeight } = temp.rect;
|
|
51
|
+
const { left, top, width, height } = event.currentTarget.getBoundingClientRect();
|
|
52
|
+
if (event.clientX - left > Math.min(startWidth, width) || event.clientY - top > Math.min(startHeight, height))
|
|
30
53
|
return;
|
|
31
54
|
setSelected(i);
|
|
32
55
|
setItems(() => {
|
|
33
|
-
const items = temp.filter((_, i) => i !== start);
|
|
34
|
-
items.splice(i, 0, temp[start]);
|
|
56
|
+
const items = temp.items.filter((_, i) => i !== start);
|
|
57
|
+
items.splice(i, 0, temp.items[start]);
|
|
35
58
|
return items;
|
|
36
59
|
});
|
|
60
|
+
setIsAnimating(true);
|
|
61
|
+
setTimeout(() => setIsAnimating(false), animationDuration);
|
|
37
62
|
}, onDragEnd: event => {
|
|
38
63
|
event.stopPropagation();
|
|
39
64
|
if (i !== start)
|
|
40
|
-
onPositionChange === null || onPositionChange === void 0 ? void 0 : onPositionChange({ start, end: i, oldItems: temp, newItems: items });
|
|
65
|
+
onPositionChange === null || onPositionChange === void 0 ? void 0 : onPositionChange({ start, end: i, oldItems: temp.items, newItems: items });
|
|
41
66
|
setStart(-1);
|
|
42
67
|
setSelected(-1);
|
|
43
|
-
} }, child);
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
function ReorderItem({ useOnlyIconToDrag, opacity, onDragStart, onDragEnter, onDragEnd, children }) {
|
|
47
|
-
const [draggable, setDraggable] = useState(!useOnlyIconToDrag);
|
|
48
|
-
const recursiveChildren = useMemo(() => recursiveClone(children), [children]);
|
|
49
|
-
function recursiveClone(children) {
|
|
50
|
-
return Children.map(children, child => {
|
|
51
|
-
if (!isValidElement(child))
|
|
52
|
-
return child;
|
|
53
|
-
const childProps = {};
|
|
54
|
-
if (useOnlyIconToDrag && child.type.name === 'ReorderIcon') {
|
|
55
|
-
childProps.onPointerEnter = () => setDraggable(true);
|
|
56
|
-
childProps.onPointerLeave = () => setDraggable(false);
|
|
57
|
-
}
|
|
58
|
-
return cloneElement(child, Object.assign({ children: recursiveClone(child.props.children) }, childProps));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return React.createElement("div", { draggable: draggable, onDragStart: onDragStart, onDragEnter: onDragEnter, onDragEnd: onDragEnd, style: { opacity } }, recursiveChildren);
|
|
68
|
+
}, onPointerEnter: () => setDraggable(true), onPointerLeave: () => setDraggable(false) }, child);
|
|
69
|
+
})));
|
|
62
70
|
}
|
|
63
71
|
export const ReorderIcon = (_a) => {
|
|
64
72
|
var { children = React.createElement(PiDotsSixVerticalBold, null), style } = _a, props = __rest(_a, ["children", "style"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-reorder-list",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A simple react component that facilitates the reordering of JSX/HTML elements through drag-and-drop functionality, allowing for easy position changes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|