react-reorder-list 0.8.6 → 0.10.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 CHANGED
@@ -4,10 +4,11 @@ A simple react component that facilitates the reordering of JSX/HTML elements th
4
4
 
5
5
  ## Features
6
6
 
7
- - Reorders list of elements using drag and drop.
7
+ - Reorders list of elements using drag and drop
8
8
  - Easy to use
9
- - Smooth transition using animation.
10
- - Listens to children updates. See [listen to children updates](#listen-to-children-updates)
9
+ - Smooth transition using animation
10
+ - Automatically syncs with children updates
11
+ - Preserves user's custom order when children change
11
12
  - Disables reordering for individual children. See [disable reordering for individual children](#disable-reordering-for-individual-children)
12
13
  - Handles nested lists easily. See [nested list usage](#nested-list-usage)
13
14
 
@@ -63,35 +64,42 @@ If set to `false`, an item can be dragged by clicking anywhere inside the item.
63
64
  If set to `true`, an item can be dragged only using the `<ReorderIcon>` present inside the item.
64
65
 
65
66
  ```jsx
66
- import React from 'react'
67
- import ReorderList, { ReorderIcon } from 'react-reorder-list'
67
+ import React from "react";
68
+ import ReorderList, { ReorderIcon } from "react-reorder-list";
68
69
 
69
70
  export default function App() {
70
- return <ReorderList useOnlyIconToDrag={true}>
71
- {[0, 1, 2, 3, 4].map(i => {
72
- return <div key={i}>
73
- <ReorderIcon /> {/* Default icon */}
74
- <ReorderIcon>
75
- {/* Custom icon/component */}
76
- </Reordericon>
77
- <span>{i}</span>
78
- </div>
79
- })}
80
- </ReorderList>
71
+ return (
72
+ <ReorderList useOnlyIconToDrag={true}>
73
+ {[0, 1, 2, 3, 4].map((i) => {
74
+ return (
75
+ <div key={i}>
76
+ <ReorderIcon /> {/* Default icon */}
77
+ <ReorderIcon>{/* Custom icon/component */}</ReorderIcon>
78
+ <span>{i}</span>
79
+ </div>
80
+ );
81
+ })}
82
+ </ReorderList>
83
+ );
81
84
  }
82
85
  ```
83
86
 
84
- #### Listen to Children Updates
87
+ #### Handling Children Updates
85
88
 
86
- `<ReorderList>` can listen to updates to it's children components using the `watchChildrenUpdates` prop as shown below.
89
+ `<ReorderList>` automatically syncs with updates to its children components. When the children change (e.g., items added/removed, or state updates), the component intelligently handles the update based on the `preserveOrder` prop.
87
90
 
88
- If set to `false`, any updates made in children component except reordering by user won't reflect.
91
+ **With `preserveOrder={true}` (default):**
89
92
 
90
- If set to `true`, updates to children like state changes, additions/omissions of children components will reflect in real time.<br>
91
- Further if `preserveOrder` is set to false, the order in which new children appear will be maintained.<br>
92
- Whereas if `preserveOrder` is set to true, the order of existing items will be preserved as before the update occured and new items will be placed at the end irrespective of their order in children. Also, if an item is being dragged and an update occurs at that moment, that item will be placed at respective location and `onPositionChange` will be called to prevent any inconsistency.
93
+ - The user's custom reorder is preserved
94
+ - New items are added at the end
95
+ - Removed items are cleanly removed
96
+ - Any in-progress drag is cancelled to prevent inconsistencies
93
97
 
94
- NOTE: The props `watchChildrenUpdates` and `preserveOrder` should be used carefully to avoid any unexpected behaviour
98
+ **With `preserveOrder={false}:`**
99
+
100
+ - The order resets to match the new children order
101
+ - User's custom ordering is discarded
102
+ - Useful for server-controlled or filtered lists
95
103
 
96
104
  ```jsx
97
105
  import React, { useState } from "react";
@@ -100,27 +108,23 @@ import ReorderList from "react-reorder-list";
100
108
  export default function App() {
101
109
  const [array, setArray] = useState([0, 1, 2, 3, 4]);
102
110
 
103
- function setNewArray() {
104
- setArray((prev) => {
105
- const array = [];
106
- prev.forEach((_) => {
107
- do {
108
- var item = Math.floor(Math.random() * 9);
109
- } while (array.includes(item));
110
- array.push(item);
111
- });
112
- return array;
113
- });
111
+ function addItem() {
112
+ setArray((prev) => [...prev, Math.max(...prev) + 1]);
113
+ }
114
+
115
+ function removeItem() {
116
+ setArray((prev) => prev.slice(0, -1));
114
117
  }
115
118
 
116
119
  return (
117
120
  <div>
118
- <ReorderList watchChildrenUpdates={true} animationDuration={200}>
121
+ <ReorderList preserveOrder={true} animationDuration={200}>
119
122
  {array.map((i) => (
120
123
  <div key={i}>Item {i}</div>
121
124
  ))}
122
125
  </ReorderList>
123
- <button onClick={setNewArray}>Click me</button>
126
+ <button onClick={addItem}>Add Item</button>
127
+ <button onClick={removeItem}>Remove Item</button>
124
128
  </div>
125
129
  );
126
130
  }
@@ -187,16 +191,15 @@ export default function App() {
187
191
 
188
192
  Here is the full API for the `<ReorderList>` component, these properties can be set on an instance of ReorderList:
189
193
 
190
- | Parameter | Type | Required | Default | Description |
191
- | ---------------------- | ------------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
192
- | `useOnlyIconToDrag` | `boolean` | No | false | See [usage with ReorderIcon](#usage-with-reordericon) |
193
- | `selectedItemOpacity` | `number (0 to 1)` | No | 0.5 | This determines the opacity of the item being dragged, until released. |
194
- | `animationDuration` | `number` | No | 300 | The duration (in ms) of swapping animation between items. If set to 0, animation will be disabled. |
195
- | `watchChildrenUpdates` | `boolean` | No | false | Enable this to listen to any updates in children of `<ReorderList>` and update the state accordingly. See [listen to children updates](#listen-to-children-updates) |
196
- | `preserveOrder` | `boolean` | No | false | Works along woth `watchChildrenUpdates` to determine whether to preserve existing order or not. See [listen to children updates](#listen-to-children-updates) |
197
- | `onPositionChange` | [`PositionChangeHandler`](#positionchangehandler) | No | - | Function to be executed on item position change. |
198
- | `disabled` | `boolean` | No | false | When set to true, `<ReorderList>` will work as a plain `div` with no functionality. |
199
- | `props` | [`DivProps`](#divprops) | No | - | Props to customize the `<ReorderList>` component. |
194
+ | Parameter | Type | Required | Default | Description |
195
+ | --------------------- | ------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
196
+ | `useOnlyIconToDrag` | `boolean` | No | false | See [usage with ReorderIcon](#usage-with-reordericon) |
197
+ | `selectedItemOpacity` | `number (0 to 1)` | No | 0.5 | This determines the opacity of the item being dragged, until released. |
198
+ | `animationDuration` | `number` | No | 300 | The duration (in ms) of swapping animation between items. If set to 0, animation will be disabled. |
199
+ | `preserveOrder` | `boolean` | No | true | When true, preserves user's custom order when children update. When false, resets to new children order. See [handling children updates](#handling-children-updates) |
200
+ | `onPositionChange` | [`PositionChangeHandler`](#positionchangehandler) | No | - | Function to be executed on item position change. |
201
+ | `disabled` | `boolean` | No | false | When set to true, `<ReorderList>` will work as a plain `div` with no functionality. |
202
+ | `props` | [`DivProps`](#divprops) | No | - | Props to customize the `<ReorderList>` component. |
200
203
 
201
204
  ## Types
202
205
 
@@ -211,17 +214,18 @@ type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement
211
214
  ### PositionChangeHandler
212
215
 
213
216
  ```typescript
214
- import type { ReactNode } from "react";
217
+ import type { Key } from "react";
215
218
 
219
+ type Order = Key[];
216
220
  type RevertHandler = () => void;
217
- type PositionChangeParams = {
218
- start?: number; // Index of the item being dragged
219
- end?: number; // Index of the item being displaced by the starting item
220
- oldItems?: ReactNode[]; // Array of children before reordering
221
- newItems?: ReactNode[]; // Array of children after reordering
221
+
222
+ export type PositionChangeHandler = (event: {
223
+ start: number; // Index of the item being dragged
224
+ end: number; // Index where the item was dropped
225
+ oldOrder: Order; // Array of keys before reordering
226
+ newOrder: Order; // Array of keys after reordering
222
227
  revert: RevertHandler; // A fallback handler to revert the reordering
223
- };
224
- type PositionChangeHandler = (params?: PositionChangeParams) => void;
228
+ }) => void;
225
229
  ```
226
230
 
227
231
  ## License
@@ -0,0 +1,247 @@
1
+ import React2, { useRef, useState, useMemo, Children, isValidElement, createRef, useEffect, useLayoutEffect, useCallback, cloneElement } from 'react';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+ var __objRest = (source, exclude) => {
23
+ var target = {};
24
+ for (var prop in source)
25
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
26
+ target[prop] = source[prop];
27
+ if (source != null && __getOwnPropSymbols)
28
+ for (var prop of __getOwnPropSymbols(source)) {
29
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
30
+ target[prop] = source[prop];
31
+ }
32
+ return target;
33
+ };
34
+
35
+ // src/constants.ts
36
+ var scrollThreshold = { x: 10, y: 100 };
37
+ function useDraggable(initValue = false) {
38
+ const [draggable, setDraggable] = useState(initValue);
39
+ const enableDragging = useCallback(() => setDraggable(true), []);
40
+ const disableDragging = useCallback(() => setDraggable(false), []);
41
+ return { draggable, onMouseEnter: enableDragging, onMouseLeave: disableDragging, onTouchStart: enableDragging, onTouchEnd: disableDragging };
42
+ }
43
+ function useStateWithHistory(initValue) {
44
+ const [state, setState] = useState(initValue);
45
+ const [prevState, setPrevState] = useState();
46
+ function setStateWithHistory(value) {
47
+ setPrevState(state);
48
+ setState(value);
49
+ }
50
+ return [state, prevState, setStateWithHistory];
51
+ }
52
+ function PiDotsSixVerticalBold(props) {
53
+ return /* @__PURE__ */ React2.createElement("span", __spreadValues({}, props), /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 256 256", fill: "currentColor", width: "1.25rem", height: "1.25rem" }, /* @__PURE__ */ React2.createElement("path", { d: "M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16a16,16,0,1,0-16-16A16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z" })));
54
+ }
55
+ function calculateBoundingBoxes(children) {
56
+ const boundingBoxes = {};
57
+ Children.forEach(children, (child) => {
58
+ const { key } = child;
59
+ if (key) boundingBoxes[key] = child.props.ref.current.getBoundingClientRect();
60
+ });
61
+ return boundingBoxes;
62
+ }
63
+
64
+ // src/lib/utils.ts
65
+ var areOrdersEqual = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
66
+ function swap(array, i, j) {
67
+ const temp = array[i];
68
+ array[i] = array[j];
69
+ array[j] = temp;
70
+ }
71
+
72
+ // src/components.tsx
73
+ function Animation({ duration, children }) {
74
+ const [boundingBox, prevBoundingBox, setBoundingBox] = useStateWithHistory({});
75
+ useLayoutEffect(() => {
76
+ if (duration > 0) setBoundingBox(calculateBoundingBoxes(children));
77
+ else setBoundingBox({});
78
+ }, [children]);
79
+ useLayoutEffect(() => {
80
+ if (duration <= 0 || !prevBoundingBox || !Object.keys(prevBoundingBox).length) return;
81
+ children.forEach((child) => {
82
+ const { key } = child;
83
+ if (!key) return;
84
+ const domNode = child.props.ref.current;
85
+ if (!domNode) return;
86
+ const { left: prevLeft, top: prevTop } = prevBoundingBox[key] || {};
87
+ const { left, top } = boundingBox[key] || {};
88
+ const changeInX = prevLeft - left;
89
+ const changeInY = prevTop - top;
90
+ if (!changeInX && !changeInY) return;
91
+ requestAnimationFrame(() => {
92
+ domNode.style.transform = `translate(${changeInX}px, ${changeInY}px)`;
93
+ domNode.style.transition = "none";
94
+ requestAnimationFrame(() => {
95
+ domNode.style.transform = "";
96
+ domNode.style.transition = `transform ${duration}ms`;
97
+ });
98
+ });
99
+ });
100
+ }, [boundingBox]);
101
+ return children;
102
+ }
103
+ function ReorderIcon(_a) {
104
+ var _b = _a, { children = /* @__PURE__ */ React2.createElement(PiDotsSixVerticalBold, null), style } = _b, props = __objRest(_b, ["children", "style"]);
105
+ return /* @__PURE__ */ React2.createElement("span", __spreadValues({ style: __spreadValues({ cursor: "grab" }, style) }, props), children);
106
+ }
107
+ function ReorderItem(_a) {
108
+ var _b = _a, { useOnlyIconToDrag, disable, ref, style, children, onTouchEnd: propOnTouchEnd } = _b, events = __objRest(_b, ["useOnlyIconToDrag", "disable", "ref", "style", "children", "onTouchEnd"]);
109
+ const _a2 = useDraggable(), { draggable, onTouchEnd: draggableOnTouchEnd } = _a2, draggableProps = __objRest(_a2, ["draggable", "onTouchEnd"]);
110
+ const recursiveClone = (children2) => Children.map(children2, (child) => {
111
+ if (!isValidElement(child)) return child;
112
+ return cloneElement(child, child.type === ReorderIcon ? draggableProps : {}, recursiveClone(child.props.children));
113
+ });
114
+ const recursiveChildren = useMemo(() => useOnlyIconToDrag ? recursiveClone(children) : children, [useOnlyIconToDrag, children]);
115
+ return /* @__PURE__ */ React2.createElement(
116
+ "div",
117
+ __spreadValues({
118
+ ref,
119
+ draggable: !disable && draggable,
120
+ style: __spreadProps(__spreadValues({}, style), { touchAction: "pan-y", cursor: useOnlyIconToDrag ? "default" : "grab" })
121
+ }, !disable && __spreadProps(__spreadValues(__spreadValues({}, events), !useOnlyIconToDrag && draggableProps), {
122
+ onTouchEnd: (event) => {
123
+ draggableOnTouchEnd(event);
124
+ propOnTouchEnd(event);
125
+ }
126
+ })),
127
+ recursiveChildren
128
+ );
129
+ }
130
+ function ReorderList({ useOnlyIconToDrag = false, selectedItemOpacity = 0.5, animationDuration = 300, preserveOrder = true, onPositionChange, disabled = false, props, children }) {
131
+ const containerRef = useRef(null);
132
+ const itemRefs = useRef(/* @__PURE__ */ new Map());
133
+ const [order, setOrder] = useState([]);
134
+ const [dragState, setDragState] = useState();
135
+ const [isAnimating, setIsAnimating] = useState(false);
136
+ const [scroll, setScroll] = useState();
137
+ const childMap = useMemo(() => {
138
+ const map = /* @__PURE__ */ new Map();
139
+ Children.forEach(children, (child) => {
140
+ if (!isValidElement(child)) return;
141
+ const { key, props: props2 } = child;
142
+ if (!key) return;
143
+ map.set(key, { child, disabled: props2["data-disable-reorder"] });
144
+ });
145
+ return map;
146
+ }, [children]);
147
+ const orderedChildren = useMemo(() => {
148
+ if (!order.length) return [];
149
+ return order.flatMap((key, orderIndex) => {
150
+ const { child, disabled: disabled2 } = childMap.get(key) || {};
151
+ if (!isValidElement(child)) return [];
152
+ const ref = getRef(key);
153
+ const isSelected = (dragState == null ? void 0 : dragState.currentIndex) === orderIndex;
154
+ return /* @__PURE__ */ React2.createElement(
155
+ ReorderItem,
156
+ {
157
+ key,
158
+ ref,
159
+ useOnlyIconToDrag,
160
+ disable: disabled2,
161
+ style: { opacity: isSelected ? selectedItemOpacity : 1 },
162
+ onDragStart: (event) => {
163
+ var _a, _b;
164
+ event.stopPropagation();
165
+ setDragState({ startIndex: orderIndex, currentIndex: orderIndex, startOrder: [...order], startRect: ((_b = (_a = ref.current) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a)) || void 0 });
166
+ },
167
+ onDragEnter: (event) => {
168
+ event.stopPropagation();
169
+ if (!dragState || dragState.currentIndex === orderIndex || isAnimating) return;
170
+ const { width: startWidth, height: startHeight } = dragState.startRect || { width: 0, height: 0 };
171
+ const { left, top, width, height } = event.currentTarget.getBoundingClientRect();
172
+ if (event.clientX - left > Math.min(startWidth, width) || event.clientY - top > Math.min(startHeight, height)) return;
173
+ setDragState((prev) => prev ? __spreadProps(__spreadValues({}, prev), { currentIndex: orderIndex }) : void 0);
174
+ setOrder(() => {
175
+ var _a;
176
+ const newOrder = [...dragState.startOrder];
177
+ const shiftForward = dragState.startIndex < orderIndex;
178
+ const increment = shiftForward ? 1 : -1;
179
+ let currentPos = dragState.startIndex;
180
+ for (let index = dragState.startIndex + increment; shiftForward ? index <= orderIndex : index >= orderIndex; index += increment) {
181
+ const key2 = dragState.startOrder[index];
182
+ if ((_a = childMap.get(key2)) == null ? void 0 : _a.disabled) continue;
183
+ swap(newOrder, currentPos, index);
184
+ currentPos = index;
185
+ }
186
+ return newOrder;
187
+ });
188
+ setIsAnimating(true);
189
+ setTimeout(() => setIsAnimating(false), animationDuration);
190
+ },
191
+ onDragEnd: handleDragEnd,
192
+ onTouchMove: (event) => {
193
+ if (!dragState) return;
194
+ const { clientX, screenX, clientY, screenY } = event.touches[0];
195
+ const left = clientX < scrollThreshold.x ? -5 : innerWidth - screenX < scrollThreshold.x ? 5 : 0;
196
+ const top = clientY < scrollThreshold.y ? -10 : innerHeight - screenY < scrollThreshold.y ? 10 : 0;
197
+ setScroll(left || top ? { left, top } : void 0);
198
+ },
199
+ onTouchEnd: () => setScroll(void 0)
200
+ },
201
+ child
202
+ );
203
+ });
204
+ }, [childMap, order, dragState, isAnimating, useOnlyIconToDrag, selectedItemOpacity, animationDuration]);
205
+ function getRef(key) {
206
+ if (!itemRefs.current.has(key)) itemRefs.current.set(key, createRef());
207
+ return itemRefs.current.get(key);
208
+ }
209
+ function handleDragEnd(event) {
210
+ if (event) {
211
+ event.stopPropagation();
212
+ if (dragState && dragState.currentIndex !== dragState.startIndex) onPositionChange == null ? void 0 : onPositionChange({ start: dragState.startIndex, end: dragState.currentIndex, oldOrder: dragState.startOrder, newOrder: order, revert: () => setOrder(dragState.startOrder) });
213
+ }
214
+ setDragState(void 0);
215
+ setScroll(void 0);
216
+ }
217
+ useEffect(() => {
218
+ const currentKeys = [];
219
+ Children.forEach(children, (child) => {
220
+ const { key } = child;
221
+ if (key) currentKeys.push(key);
222
+ });
223
+ let newOrder;
224
+ if (preserveOrder) {
225
+ const currentKeySet = new Set(currentKeys);
226
+ const newKeys = currentKeys.filter((key) => !order.includes(key));
227
+ const filteredOrder = order.filter((key) => currentKeySet.has(key));
228
+ newOrder = [...filteredOrder, ...newKeys];
229
+ } else newOrder = currentKeys;
230
+ if (!areOrdersEqual(order, newOrder)) {
231
+ if (dragState) setDragState(void 0);
232
+ setOrder(newOrder);
233
+ }
234
+ }, [children]);
235
+ useEffect(() => {
236
+ if (!scroll) return;
237
+ const { left, top } = scroll;
238
+ const { scrollWidth, scrollHeight } = document.body;
239
+ const interval = setInterval(() => {
240
+ if (left < 0 || top < 0 || scrollWidth - scrollX > innerWidth - left || scrollHeight - scrollY > innerHeight - top) scrollBy({ left, top, behavior: "instant" });
241
+ }, 20);
242
+ return () => clearInterval(interval);
243
+ }, [scroll]);
244
+ return /* @__PURE__ */ React2.createElement("div", __spreadValues({ ref: containerRef }, props), disabled ? children : /* @__PURE__ */ React2.createElement(Animation, { duration: dragState && !scroll ? animationDuration : 0 }, orderedChildren));
245
+ }
246
+
247
+ export { ReorderIcon, ReorderList };
@@ -2,6 +2,6 @@ import { JSX } from 'react';
2
2
  import { ReorderListProps, DivProps } from './types.js';
3
3
 
4
4
  declare function ReorderIcon({ children, style, ...props }: DivProps): JSX.Element;
5
- declare function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, animationDuration, watchChildrenUpdates, preserveOrder, onPositionChange, disabled, props, children }: ReorderListProps): JSX.Element;
5
+ declare function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, animationDuration, preserveOrder, onPositionChange, disabled, props, children }: ReorderListProps): JSX.Element;
6
6
 
7
7
  export { ReorderIcon, ReorderList as default };
@@ -1 +1 @@
1
- export { ReorderIcon, ReorderList as default } from './chunks/chunk-6VGR3NNS.js';
1
+ export { ReorderIcon, ReorderList as default } from './chunks/chunk-KM7ZPSG2.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { ReorderIcon, ReorderList as default } from './chunks/chunk-6VGR3NNS.js';
1
+ export { ReorderIcon, ReorderList as default } from './chunks/chunk-KM7ZPSG2.js';
package/dist/types.d.ts CHANGED
@@ -1,31 +1,30 @@
1
- import { ReactNode, DetailedHTMLProps, HTMLAttributes, ReactElement, RefObject, DragEventHandler, MouseEventHandler, TouchEventHandler, CSSProperties } from 'react';
1
+ import { Key, DetailedHTMLProps, HTMLAttributes, ReactNode, ReactElement, RefObject, DragEventHandler, MouseEventHandler, TouchEventHandler, CSSProperties } from 'react';
2
2
 
3
- type BoundingBox = {
4
- [key: string]: DOMRect;
3
+ type AnimationProps = {
4
+ duration: number;
5
+ children: Child[];
5
6
  };
7
+ type BoundingBox = Record<string, DOMRect>;
6
8
  type Child = ReactElement<{
7
9
  ref: RefObject<HTMLElement>;
8
10
  }>;
9
- type AnimationProps = {
10
- duration: number;
11
- children: ReactNode;
12
- };
13
- type IconProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
11
+ type Order = Key[];
14
12
  type DivDragEventHandler = DragEventHandler<HTMLDivElement>;
15
13
  type DivMouseEventHandler = MouseEventHandler<HTMLDivElement>;
16
14
  type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
15
+ type DivRef = RefObject<HTMLDivElement | null>;
17
16
  type DivTouchEventHandler = TouchEventHandler<HTMLDivElement>;
18
17
  type PositionChangeHandler = (event: {
19
18
  start: number;
20
19
  end: number;
21
- oldItems: ReactNode[];
22
- newItems: ReactNode[];
23
- revert: Function;
20
+ oldOrder: Order;
21
+ newOrder: Order;
22
+ revert: RevertHandler;
24
23
  }) => void;
25
24
  type ReorderItemProps = {
26
25
  useOnlyIconToDrag: boolean;
27
- disable: boolean;
28
- ref: RefObject<HTMLDivElement | null>;
26
+ disable?: boolean;
27
+ ref: DivRef;
29
28
  style: CSSProperties;
30
29
  onDragStart?: DivDragEventHandler;
31
30
  onDragEnter: DivDragEventHandler;
@@ -38,12 +37,13 @@ type ReorderListProps = {
38
37
  useOnlyIconToDrag?: boolean;
39
38
  selectedItemOpacity?: number;
40
39
  animationDuration?: number;
41
- watchChildrenUpdates?: boolean;
42
40
  preserveOrder?: boolean;
43
41
  onPositionChange?: PositionChangeHandler;
44
42
  disabled?: boolean;
45
43
  props?: DivProps;
46
44
  children?: ReactNode;
47
45
  };
46
+ type RevertHandler = () => void;
47
+ type IconProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
48
48
 
49
- export type { AnimationProps, BoundingBox, Child, DivDragEventHandler, DivMouseEventHandler, DivProps, DivTouchEventHandler, IconProps, PositionChangeHandler, ReorderItemProps, ReorderListProps };
49
+ export type { AnimationProps, BoundingBox, Child, DivDragEventHandler, DivMouseEventHandler, DivProps, DivRef, DivTouchEventHandler, IconProps, Order, PositionChangeHandler, ReorderItemProps, ReorderListProps, RevertHandler };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-reorder-list",
3
- "version": "0.8.6",
3
+ "version": "0.10.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
  "license": "MIT",
6
6
  "author": "Sahil Aggarwal <aggarwalsahil2004@gmail.com>",
@@ -31,10 +31,10 @@
31
31
  "react": "^19.0.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@release-it/conventional-changelog": "^10.0.4",
35
- "@types/react": "^19.2.7",
34
+ "@release-it/conventional-changelog": "^10.0.5",
35
+ "@types/react": "^19.2.14",
36
36
  "prettier-package-json": "^2.8.0",
37
- "release-it": "^19.2.2",
37
+ "release-it": "^19.2.4",
38
38
  "tsup": "^8.5.1",
39
39
  "typescript": "^5.9.3"
40
40
  },
@@ -1,235 +0,0 @@
1
- import React2, { useRef, useState, Children, useMemo, createRef, useEffect, isValidElement, useLayoutEffect, useCallback, cloneElement } from 'react';
2
-
3
- var __defProp = Object.defineProperty;
4
- var __defProps = Object.defineProperties;
5
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
- var __spreadValues = (a, b) => {
11
- for (var prop in b || (b = {}))
12
- if (__hasOwnProp.call(b, prop))
13
- __defNormalProp(a, prop, b[prop]);
14
- if (__getOwnPropSymbols)
15
- for (var prop of __getOwnPropSymbols(b)) {
16
- if (__propIsEnum.call(b, prop))
17
- __defNormalProp(a, prop, b[prop]);
18
- }
19
- return a;
20
- };
21
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
- var __objRest = (source, exclude) => {
23
- var target = {};
24
- for (var prop in source)
25
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
26
- target[prop] = source[prop];
27
- if (source != null && __getOwnPropSymbols)
28
- for (var prop of __getOwnPropSymbols(source)) {
29
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
30
- target[prop] = source[prop];
31
- }
32
- return target;
33
- };
34
- function useDraggable(initValue = false) {
35
- const [draggable, setDraggable] = useState(initValue);
36
- const enableDragging = useCallback(() => setDraggable(true), []);
37
- const disableDragging = useCallback(() => setDraggable(false), []);
38
- return { draggable, onMouseEnter: enableDragging, onMouseLeave: disableDragging, onTouchStart: enableDragging, onTouchEnd: disableDragging };
39
- }
40
- function useStateWithHistory(initValue) {
41
- const [state, setState] = useState(initValue);
42
- const [prevState, setPrevState] = useState();
43
- function setStateWithHistory(value) {
44
- setPrevState(state);
45
- setState(value);
46
- }
47
- return [state, prevState, setStateWithHistory];
48
- }
49
- var getKey = (child) => {
50
- var _a;
51
- return (_a = child == null ? void 0 : child.key) == null ? void 0 : _a.split("/.")[0];
52
- };
53
- function calculateBoundingBoxes(children) {
54
- const boundingBoxes = {};
55
- Children.forEach(children, (child) => {
56
- const key = getKey(child);
57
- if (key) boundingBoxes[key] = child.props.ref.current.getBoundingClientRect();
58
- });
59
- return boundingBoxes;
60
- }
61
-
62
- // src/constants.ts
63
- var scrollThreshold = { x: 10, y: 100 };
64
- function PiDotsSixVerticalBold(props) {
65
- return /* @__PURE__ */ React2.createElement("span", __spreadValues({}, props), /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 256 256", fill: "currentColor", width: "1.25rem", height: "1.25rem" }, /* @__PURE__ */ React2.createElement("path", { d: "M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16a16,16,0,1,0-16-16A16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z" })));
66
- }
67
-
68
- // src/lib/utils.ts
69
- function swap(array, i, j) {
70
- const temp = array[i];
71
- array[i] = array[j];
72
- array[j] = temp;
73
- }
74
-
75
- // src/components.tsx
76
- if (typeof window !== "undefined") import('drag-drop-touch');
77
- function Animation({ duration, children }) {
78
- const [boundingBox, prevBoundingBox, setBoundingBox] = useStateWithHistory({});
79
- useLayoutEffect(() => {
80
- if (duration > 0) setBoundingBox(calculateBoundingBoxes(children));
81
- else setBoundingBox({});
82
- }, [children]);
83
- useLayoutEffect(() => {
84
- if (duration > 0 && prevBoundingBox && Object.keys(prevBoundingBox).length)
85
- Children.forEach(children, (child) => {
86
- const key = getKey(child);
87
- if (!key) return;
88
- const domNode = child.props.ref.current;
89
- const { left: prevLeft, top: prevTop } = prevBoundingBox[key] || {};
90
- const { left, top } = boundingBox[key];
91
- const changeInX = prevLeft - left, changeInY = prevTop - top;
92
- if (changeInX || changeInY)
93
- requestAnimationFrame(() => {
94
- domNode.style.transform = `translate(${changeInX}px, ${changeInY}px)`;
95
- domNode.style.transition = "none";
96
- requestAnimationFrame(() => {
97
- domNode.style.transform = "";
98
- domNode.style.transition = `transform ${duration}ms`;
99
- });
100
- });
101
- });
102
- }, [boundingBox]);
103
- return children;
104
- }
105
- function ReorderIcon(_a) {
106
- var _b = _a, { children = /* @__PURE__ */ React2.createElement(PiDotsSixVerticalBold, null), style } = _b, props = __objRest(_b, ["children", "style"]);
107
- return /* @__PURE__ */ React2.createElement("span", __spreadValues({ style: __spreadValues({ cursor: "grab" }, style) }, props), children);
108
- }
109
- function ReorderItem(_a) {
110
- var _b = _a, { useOnlyIconToDrag, disable, ref, style, children, onTouchEnd: propOnTouchEnd } = _b, events = __objRest(_b, ["useOnlyIconToDrag", "disable", "ref", "style", "children", "onTouchEnd"]);
111
- const _a2 = useDraggable(), { draggable, onTouchEnd: draggableOnTouchEnd } = _a2, draggableProps = __objRest(_a2, ["draggable", "onTouchEnd"]);
112
- const recursiveClone = (children2) => Children.map(children2, (child) => {
113
- if (!isValidElement(child)) return child;
114
- return cloneElement(child, child.type === ReorderIcon ? draggableProps : {}, recursiveClone(child.props.children));
115
- });
116
- const recursiveChildren = useMemo(() => useOnlyIconToDrag ? recursiveClone(children) : children, [useOnlyIconToDrag, children]);
117
- return /* @__PURE__ */ React2.createElement(
118
- "div",
119
- __spreadValues({
120
- ref,
121
- draggable: !disable && draggable,
122
- style: __spreadProps(__spreadValues({}, style), { touchAction: "pan-y", cursor: useOnlyIconToDrag ? "default" : "grab" })
123
- }, !disable && __spreadProps(__spreadValues(__spreadValues({}, events), !useOnlyIconToDrag && draggableProps), {
124
- onTouchEnd: (event) => {
125
- draggableOnTouchEnd(event);
126
- propOnTouchEnd(event);
127
- }
128
- })),
129
- recursiveChildren
130
- );
131
- }
132
- function ReorderList({ useOnlyIconToDrag = false, selectedItemOpacity = 0.5, animationDuration = 300, watchChildrenUpdates = false, preserveOrder = false, onPositionChange, disabled = false, props, children }) {
133
- const ref = useRef(null);
134
- const [start, setStart] = useState(-1);
135
- const [selected, setSelected] = useState(-1);
136
- const [items, setItems] = useState(Children.toArray(children));
137
- const [temp, setTemp] = useState({ items: [] });
138
- const [isAnimating, setIsAnimating] = useState(false);
139
- const [scroll, setScroll] = useState();
140
- const [refs, disableArr] = useMemo(
141
- () => items.reduce(
142
- ([refs2, disableArr2], item) => {
143
- var _a;
144
- return [refs2.concat(createRef()), disableArr2.concat((_a = item == null ? void 0 : item.props) == null ? void 0 : _a["data-disable-reorder"])];
145
- },
146
- [[], []]
147
- ),
148
- [items]
149
- );
150
- const findIndex = (key) => key ? items.findIndex((item) => (item == null ? void 0 : item.key) === key) : -1;
151
- useEffect(() => {
152
- if (!watchChildrenUpdates) return;
153
- if (selected !== -1) handleDragEnd(null, selected, preserveOrder);
154
- const items2 = [];
155
- const newItems = [];
156
- Children.forEach(children, (child, index) => {
157
- if (preserveOrder) index = findIndex(child == null ? void 0 : child.key);
158
- if (index === -1) newItems.push(child);
159
- else items2[index] = child;
160
- });
161
- setItems(items2.filter((item) => item !== void 0).concat(newItems));
162
- }, [children]);
163
- useEffect(() => {
164
- if (!scroll) return;
165
- const { left, top } = scroll;
166
- const { scrollWidth, scrollHeight } = document.body;
167
- const interval = setInterval(() => {
168
- if (left < 0 || top < 0 || scrollWidth - scrollX > innerWidth - left || scrollHeight - scrollY > innerHeight - top) scrollBy({ left, top, behavior: "instant" });
169
- }, 20);
170
- return () => clearInterval(interval);
171
- }, [scroll]);
172
- function handleDragEnd(event, end = selected, handlePositionChange = true) {
173
- event == null ? void 0 : event.stopPropagation();
174
- if (handlePositionChange && end !== start) onPositionChange == null ? void 0 : onPositionChange({ start, end, oldItems: temp.items, newItems: items, revert: () => setItems(temp.items) });
175
- setStart(-1);
176
- setSelected(-1);
177
- }
178
- return /* @__PURE__ */ React2.createElement("div", __spreadValues({ ref }, props), disabled ? children : /* @__PURE__ */ React2.createElement(Animation, { duration: +(start !== -1 && !scroll) && animationDuration }, items.map((child, i) => {
179
- if (!isValidElement(child)) return child;
180
- return /* @__PURE__ */ React2.createElement(
181
- ReorderItem,
182
- {
183
- key: child.key,
184
- ref: refs[i],
185
- useOnlyIconToDrag,
186
- disable: disableArr[i],
187
- style: { opacity: selected === i ? selectedItemOpacity : 1 },
188
- onDragStart: (event) => {
189
- var _a, _b;
190
- event.stopPropagation();
191
- setStart(i);
192
- setSelected(i);
193
- setTemp({ items, rect: ((_b = (_a = ref.current.childNodes[i]).getBoundingClientRect) == null ? void 0 : _b.call(_a)) || {} });
194
- },
195
- onDragEnter: (event) => {
196
- event.stopPropagation();
197
- if (start === -1 || selected === i || isAnimating) return;
198
- const { width: startWidth, height: startHeight } = temp.rect;
199
- const { left, top, width, height } = event.target.getBoundingClientRect();
200
- if (event.clientX - left > Math.min(startWidth, width) || event.clientY - top > Math.min(startHeight, height)) return;
201
- setSelected(i);
202
- setItems(() => {
203
- const items2 = temp.items.slice();
204
- const shiftForward = start < i;
205
- const increment = shiftForward ? 1 : -1;
206
- let key = start;
207
- for (let index = start + increment; shiftForward ? index <= i : index >= i; index += increment) {
208
- if (disableArr[index]) continue;
209
- swap(items2, key, index);
210
- key = index;
211
- }
212
- return items2;
213
- });
214
- setIsAnimating(true);
215
- setTimeout(() => setIsAnimating(false), animationDuration);
216
- },
217
- onDragEnd: handleDragEnd,
218
- onTouchMove: (event) => {
219
- if (start === -1) return;
220
- const { clientX, screenX, clientY, screenY } = event.touches[0];
221
- let left = 0, top = 0;
222
- if (clientX < scrollThreshold.x) left = -5;
223
- else if (innerWidth - screenX < scrollThreshold.x) left = 5;
224
- if (clientY < scrollThreshold.y) top = -10;
225
- else if (innerHeight - screenY < scrollThreshold.y) top = 10;
226
- setScroll(left || top ? { left, top } : void 0);
227
- },
228
- onTouchEnd: () => setScroll(void 0)
229
- },
230
- child
231
- );
232
- })));
233
- }
234
-
235
- export { ReorderIcon, ReorderList };