react-reorder-list
Advanced tools
Comparing version 0.2.1 to 0.3.0
@@ -1,2 +0,2 @@ | ||
import React, { useState, useLayoutEffect, Children, isValidElement, useEffect, useRef } from "react"; | ||
import React, { useState, useLayoutEffect, Children, useEffect, useRef } from "react"; | ||
function usePrevious(value) { | ||
@@ -10,6 +10,3 @@ const prevChildrenRef = useRef(); | ||
const boundingBoxes = {}; | ||
Children.forEach(children, (child, i) => { | ||
if (isValidElement(child)) | ||
boundingBoxes[child.key || i] = child.ref.current.getBoundingClientRect(); | ||
}); | ||
Children.forEach(children, child => boundingBoxes[child.key.split("/.")[0]] = child.ref.current.getBoundingClientRect()); | ||
return boundingBoxes; | ||
@@ -27,8 +24,7 @@ } | ||
if (prevBoundingBox && Object.keys(prevBoundingBox).length) | ||
Children.forEach(children, (child, i) => { | ||
if (!isValidElement(child)) | ||
return; | ||
Children.forEach(children, child => { | ||
const domNode = child.ref.current; | ||
const { left: prevLeft, top: prevTop } = prevBoundingBox[child.key || i]; | ||
const { left, top } = boundingBox[child.key || i]; | ||
const key = child.key.split("/.")[0]; | ||
const { left: prevLeft, top: prevTop } = prevBoundingBox[key] || {}; | ||
const { left, top } = boundingBox[key]; | ||
const changeInX = prevLeft - left, changeInY = prevTop - top; | ||
@@ -50,3 +46,3 @@ if (changeInX || changeInY) | ||
export default function Animation({ duration, children }) { | ||
return duration ? React.createElement(Animate, { duration: duration }, children) : children; | ||
return duration > 0 ? React.createElement(Animate, { duration: duration }, children) : children; | ||
} |
@@ -1,2 +0,2 @@ | ||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'; | ||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react"; | ||
export type Props = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>; | ||
@@ -13,2 +13,3 @@ export type PositionChangeHandler = (params?: { | ||
animationDuration?: number; | ||
watchChildrenUpdates: boolean; | ||
onPositionChange?: PositionChangeHandler; | ||
@@ -20,3 +21,3 @@ disable?: boolean; | ||
export type { IconProps } from './icons.js'; | ||
export default function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, animationDuration, onPositionChange, disable, props, children }: ReorderListProps): React.JSX.Element; | ||
export default function ReorderList({ useOnlyIconToDrag, selectedItemOpacity, animationDuration, watchChildrenUpdates, onPositionChange, disable, props, children }: ReorderListProps): React.JSX.Element; | ||
export declare const ReorderIcon: ({ children, style, ...props }: Props) => React.JSX.Element; |
@@ -12,32 +12,33 @@ var __rest = (this && this.__rest) || function (s, e) { | ||
}; | ||
import React, { Children, cloneElement, createRef, forwardRef, isValidElement, useMemo, useRef, useState } from 'react'; | ||
import { PiDotsSixVerticalBold } from './icons.js'; | ||
import Animation from './animation.js'; | ||
const ReorderItem = forwardRef(({ useOnlyIconToDrag, draggable, style, onDragStart, onDragEnter, onDragEnd, onPointerEnter, onPointerLeave, children }, ref) => { | ||
const props = draggable ? { draggable, onDragStart, onDragEnter, onDragEnd } : {}; | ||
const recursiveClone = (children) => Children.map(children, child => { | ||
if (!isValidElement(child)) | ||
return child; | ||
const childProps = {}; | ||
if (useOnlyIconToDrag && child.type.name === 'ReorderIcon') { | ||
childProps.onPointerEnter = onPointerEnter; | ||
childProps.onPointerLeave = onPointerLeave; | ||
} | ||
return cloneElement(child, Object.assign({ children: recursiveClone(child.props.children) }, childProps)); | ||
}); | ||
const recursiveChildren = useMemo(() => recursiveClone(children), [children]); | ||
return React.createElement("div", Object.assign({ ref: ref, style: style }, props), recursiveChildren); | ||
}); | ||
export default function ReorderList({ useOnlyIconToDrag = false, selectedItemOpacity = 0.5, animationDuration = 400, onPositionChange, disable = false, props, children }) { | ||
const ref = useRef(); | ||
const [draggable, setDraggable] = useState(!useOnlyIconToDrag); | ||
import React, { Children, cloneElement, createRef, forwardRef, isValidElement, useEffect, useMemo, useRef, useState } from "react"; | ||
import { PiDotsSixVerticalBold } from "./icons.js"; | ||
import Animation from "./animation.js"; | ||
const ReorderItemRef = forwardRef(ReorderItem); | ||
export default function ReorderList({ useOnlyIconToDrag = false, selectedItemOpacity = 0.5, animationDuration = 400, watchChildrenUpdates = false, onPositionChange, disable = false, props, children }) { | ||
const ref = useRef(null); | ||
const [draggable, setDraggable] = useState(false); | ||
const [start, setStart] = useState(-1); | ||
const [selected, setSelected] = useState(-1); | ||
const [items, setItems] = useState(children); | ||
const [items, setItems] = useState(Children.toArray(children)); | ||
const [temp, setTemp] = useState({}); | ||
const [isAnimating, setIsAnimating] = useState(false); | ||
const findIndex = (key) => key ? items.findIndex(item => { var _a, _b; return ((_b = (_a = item === null || item === void 0 ? void 0 : item.key) === null || _a === void 0 ? void 0 : _a.split(".$").at(-1)) !== null && _b !== void 0 ? _b : item === null || item === void 0 ? void 0 : item.toString()) === key; }) : -1; | ||
useEffect(() => { | ||
if (!watchChildrenUpdates) | ||
return; | ||
const items = []; | ||
const newItems = []; | ||
Children.forEach(children, child => { | ||
var _a; | ||
const index = findIndex((_a = child === null || child === void 0 ? void 0 : child.key) !== null && _a !== void 0 ? _a : child === null || child === void 0 ? void 0 : child.toString()); | ||
if (index === -1) | ||
newItems.push(child); | ||
else | ||
items[index] = child; | ||
}); | ||
setItems(items.filter(item => item !== undefined).concat(newItems)); | ||
}, [children]); | ||
return React.createElement("div", Object.assign({ ref: ref }, props), disable ? children : React.createElement(Animation, { duration: +draggable && animationDuration }, Children.map(items, (child, i) => { | ||
if (!isValidElement(child)) | ||
return child; | ||
return React.createElement(ReorderItem, { key: child.key || i, ref: createRef(), useOnlyIconToDrag: useOnlyIconToDrag, draggable: draggable, style: { opacity: selected === i ? selectedItemOpacity : 1 }, onDragStart: event => { | ||
var _a; | ||
return React.createElement(ReorderItemRef, { key: (_a = child === null || child === void 0 ? void 0 : child.key) !== null && _a !== void 0 ? _a : child === null || child === void 0 ? void 0 : child.toString(), ref: createRef(), useOnlyIconToDrag: useOnlyIconToDrag, draggable: draggable, style: { opacity: selected === i ? selectedItemOpacity : 1 }, onDragStart: event => { | ||
event.stopPropagation(); | ||
@@ -72,2 +73,15 @@ setStart(i); | ||
} | ||
function ReorderItem({ useOnlyIconToDrag, draggable, style, onDragStart, onDragEnter, onDragEnd, onPointerEnter, onPointerLeave, children }, ref) { | ||
let props = useOnlyIconToDrag ? {} : { onPointerEnter, onPointerLeave }; | ||
if (draggable) | ||
props = Object.assign(Object.assign({}, props), { draggable, onDragStart, onDragEnter, onDragEnd }); | ||
const recursiveClone = (children) => Children.map(children, child => { | ||
if (!isValidElement(child)) | ||
return child; | ||
const childProps = useOnlyIconToDrag && child.type.name === 'ReorderIcon' ? { onPointerEnter, onPointerLeave } : {}; | ||
return cloneElement(child, Object.assign({ children: recursiveClone(child.props.children) }, childProps)); | ||
}); | ||
const recursiveChildren = useMemo(() => recursiveClone(children), [children]); | ||
return React.createElement("div", Object.assign({ ref: ref, style: style }, props), recursiveChildren); | ||
} | ||
export const ReorderIcon = (_a) => { | ||
@@ -74,0 +88,0 @@ var { children = React.createElement(PiDotsSixVerticalBold, null), style } = _a, props = __rest(_a, ["children", "style"]); |
{ | ||
"name": "react-reorder-list", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "A simple react component that facilitates the reordering of JSX/HTML elements through drag-and-drop functionality, allowing for easy position changes.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -6,3 +6,4 @@ # react-reorder-list | ||
- Easy to use | ||
- Fully Customizable | ||
- Smooth transition using animation. | ||
- Listen to children updates. See [listen to children updates](#listen-to-children-updates) | ||
- Handles nested lists easily. See [nested list usage](#nested-list-usage) | ||
@@ -38,3 +39,3 @@ ## Installation | ||
{/* Having a unique key is important */} | ||
return <div key={i}>{i}</div> | ||
return <div key={i}>Item {i}</div> | ||
})} | ||
@@ -68,2 +69,36 @@ </ReorderList> | ||
``` | ||
#### Listen to Children Updates | ||
`<ReorderList>` can listen to updates to it's children components using the `watchChildrenUpdates` prop as shown below. | ||
If set to `true`, updates to children like state changes, additions/omissions of children components will reflect in real time. | ||
If set to `false`, any updates made in children component except reordering won't reflect. | ||
```jsx | ||
import React, { useState } from 'react' | ||
import ReorderList from 'react-reorder-list' | ||
export default function App() { | ||
const [array, setArray] = useState([0, 1, 2, 3, 4]) | ||
function setNewArray() { | ||
setArray(prev => { | ||
const array = [] | ||
prev.forEach(_ => { | ||
do { | ||
var item = Math.floor(Math.random() * 9) | ||
} while (array.includes(item)) | ||
array.push(item) | ||
}) | ||
return array | ||
}) | ||
} | ||
return <div> | ||
<ReorderList watchChildrenUpdates={true} animationDuration={200}> | ||
{array.map(i => <div key={i}>Item {i}</div>)} | ||
</ReorderList> | ||
<button onClick={setNewArray}>Click me</button> | ||
</div> | ||
} | ||
``` | ||
#### Nested List Usage | ||
@@ -99,5 +134,6 @@ ```jsx | ||
| `selectedItemOpacity` | `Number (0 to 1)` | No | 0.5 | This determines the opacity of the item being dragged, until released. | | ||
| `animationDuration` | `Number` | No | 400 | The duration of swapping animation between items. If set to 0, animation will be disabled. | | ||
| `animationDuration` | `Number` | No | 400 | The duration (in ms) of swapping animation between items. If set to 0, animation will be disabled. | | ||
| `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) | | ||
| `onPositionChange` | [`PositionChangeHandler`](#positionchangehandler) | No | - | Function to be executed on item position change. | | ||
| `disable` | `Boolean` | No | false | When set to true, `ReorderList` will work as a plain `div` with no functionality. | | ||
| `disable` | `Boolean` | No | false | When set to true, `<ReorderList>` will work as a plain `div` with no functionality. | | ||
| `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<ReorderList>` component. | | ||
@@ -104,0 +140,0 @@ ### Types |
17410
172
143