@marciocamello/react-sortable-tree
Version:
Drag-and-drop sortable component for nested data and hierarchies
1,810 lines (1,797 loc) • 56.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var withScrolling = require('@nosferatu500/react-dnd-scrollzone');
var isEqual = require('lodash.isequal');
var reactDnd = require('react-dnd');
var reactDndHtml5Backend = require('react-dnd-html5-backend');
var reactVirtuoso = require('react-virtuoso');
const defaultGetNodeKey = ({
treeIndex
}) => treeIndex;
const getReactElementText = parent => {
if (typeof parent === 'string') {
return parent;
}
if (parent === undefined || typeof parent !== 'object' || !parent.props || !parent.props.children || typeof parent.props.children !== 'string' && typeof parent.props.children !== 'object') {
return '';
}
if (typeof parent.props.children === 'string') {
return parent.props.children;
}
return parent.props.children.map(child => getReactElementText(child)).join('');
};
const stringSearch = (key, searchQuery, node, path, treeIndex) => {
if (typeof node[key] === 'function') {
return String(node[key]({
node,
path,
treeIndex
})).includes(searchQuery);
}
if (typeof node[key] === 'object') {
return getReactElementText(node[key]).includes(searchQuery);
}
return node[key] && String(node[key]).includes(searchQuery);
};
const defaultSearchMethod = ({
node,
path,
treeIndex,
searchQuery
}) => {
return stringSearch('title', searchQuery, node, path, treeIndex) || stringSearch('subtitle', searchQuery, node, path, treeIndex);
};
const getNodeDataAtTreeIndexOrNextIndex = ({
targetIndex,
node,
currentIndex,
getNodeKey,
path = [],
lowerSiblingCounts = [],
ignoreCollapsed = true,
isPseudoRoot = false
}) => {
const selfPath = isPseudoRoot ? [] : [...path, getNodeKey({
node,
treeIndex: currentIndex
})];
if (currentIndex === targetIndex) {
return {
node,
lowerSiblingCounts,
path: selfPath
};
}
if (!node?.children || ignoreCollapsed && node?.expanded !== true) {
return {
nextIndex: currentIndex + 1
};
}
let childIndex = currentIndex + 1;
const childCount = node.children.length;
for (let i = 0; i < childCount; i += 1) {
const result = getNodeDataAtTreeIndexOrNextIndex({
ignoreCollapsed,
getNodeKey,
targetIndex,
node: node.children[i],
currentIndex: childIndex,
lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1],
path: selfPath
});
if (result.node) {
return result;
}
childIndex = result.nextIndex;
}
return {
nextIndex: childIndex
};
};
const getDescendantCount = ({
node,
ignoreCollapsed = true
}) => {
return getNodeDataAtTreeIndexOrNextIndex({
getNodeKey: () => {},
ignoreCollapsed,
node,
currentIndex: 0,
targetIndex: -1
}).nextIndex - 1;
};
const walkDescendants = ({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot = false,
node,
parentNode = undefined,
currentIndex,
path = [],
lowerSiblingCounts = []
}) => {
const selfPath = isPseudoRoot ? [] : [...path, getNodeKey({
node,
treeIndex: currentIndex
})];
const selfInfo = isPseudoRoot ? undefined : {
node,
parentNode,
path: selfPath,
lowerSiblingCounts,
treeIndex: currentIndex
};
if (!isPseudoRoot) {
const callbackResult = callback(selfInfo);
if (callbackResult === false) {
return false;
}
}
if (!node.children || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) {
return currentIndex;
}
let childIndex = currentIndex;
const childCount = node.children.length;
if (typeof node.children !== 'function') {
for (let i = 0; i < childCount; i += 1) {
childIndex = walkDescendants({
callback,
getNodeKey,
ignoreCollapsed,
node: node.children[i],
parentNode: isPseudoRoot ? undefined : node,
currentIndex: childIndex + 1,
lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1],
path: selfPath
});
if (childIndex === false) {
return false;
}
}
}
return childIndex;
};
const mapDescendants = ({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot = false,
node,
parentNode = undefined,
currentIndex,
path = [],
lowerSiblingCounts = []
}) => {
const nextNode = {
...node
};
const selfPath = isPseudoRoot ? [] : [...path, getNodeKey({
node: nextNode,
treeIndex: currentIndex
})];
const selfInfo = {
node: nextNode,
parentNode,
path: selfPath,
lowerSiblingCounts,
treeIndex: currentIndex
};
if (!nextNode.children || nextNode.expanded !== true && ignoreCollapsed && !isPseudoRoot) {
return {
treeIndex: currentIndex,
node: callback(selfInfo)
};
}
let childIndex = currentIndex;
const childCount = nextNode.children.length;
if (typeof nextNode.children !== 'function') {
nextNode.children = nextNode.children.map((child, i) => {
const mapResult = mapDescendants({
callback,
getNodeKey,
ignoreCollapsed,
node: child,
parentNode: isPseudoRoot ? undefined : nextNode,
currentIndex: childIndex + 1,
lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1],
path: selfPath
});
childIndex = mapResult.treeIndex;
return mapResult.node;
});
}
return {
node: callback(selfInfo),
treeIndex: childIndex
};
};
const getVisibleNodeCount = ({
treeData
}) => {
const traverse = node => {
if (!node.children || node.expanded !== true || typeof node.children === 'function') {
return 1;
}
return 1 + node.children.reduce((total, currentNode) => total + traverse(currentNode), 0);
};
return treeData.reduce((total, currentNode) => total + traverse(currentNode), 0);
};
const getVisibleNodeInfoAtIndex = ({
treeData,
index: targetIndex,
getNodeKey
}) => {
if (!treeData || treeData.length === 0) {
return undefined;
}
const result = getNodeDataAtTreeIndexOrNextIndex({
targetIndex,
getNodeKey,
node: {
children: treeData,
expanded: true
},
currentIndex: -1,
path: [],
lowerSiblingCounts: [],
isPseudoRoot: true
});
if (result.node) {
return result;
}
return undefined;
};
const walk = ({
treeData,
getNodeKey,
callback,
ignoreCollapsed = true
}) => {
if (!treeData || treeData.length === 0) {
return;
}
walkDescendants({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot: true,
node: {
children: treeData
},
currentIndex: -1,
path: [],
lowerSiblingCounts: []
});
};
const map = ({
treeData,
getNodeKey,
callback,
ignoreCollapsed = true
}) => {
if (!treeData || treeData.length === 0) {
return [];
}
return mapDescendants({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot: true,
node: {
children: treeData
},
currentIndex: -1,
path: [],
lowerSiblingCounts: []
}).node.children;
};
const toggleExpandedForAll = ({
treeData,
expanded = true
}) => {
return map({
treeData,
callback: ({
node
}) => ({
...node,
expanded
}),
getNodeKey: ({
treeIndex
}) => treeIndex,
ignoreCollapsed: false
});
};
const changeNodeAtPath = ({
treeData,
path,
newNode,
getNodeKey,
ignoreCollapsed = true
}) => {
const RESULT_MISS = 'RESULT_MISS';
const traverse = ({
isPseudoRoot = false,
node,
currentTreeIndex,
pathIndex
}) => {
if (!isPseudoRoot && getNodeKey({
node,
treeIndex: currentTreeIndex
}) !== path[pathIndex]) {
return RESULT_MISS;
}
if (pathIndex >= path.length - 1) {
return typeof newNode === 'function' ? newNode({
node,
treeIndex: currentTreeIndex
}) : newNode;
}
if (!node.children) {
throw new Error('Path referenced children of node with no children.');
}
let nextTreeIndex = currentTreeIndex + 1;
for (let i = 0; i < node.children.length; i += 1) {
const result = traverse({
node: node.children[i],
currentTreeIndex: nextTreeIndex,
pathIndex: pathIndex + 1
});
if (result !== RESULT_MISS) {
if (result) {
return {
...node,
children: [...node.children.slice(0, i), result, ...node.children.slice(i + 1)]
};
}
return {
...node,
children: [...node.children.slice(0, i), ...node.children.slice(i + 1)]
};
}
nextTreeIndex += 1 + getDescendantCount({
node: node.children[i],
ignoreCollapsed
});
}
return RESULT_MISS;
};
const result = traverse({
node: {
children: treeData
},
currentTreeIndex: -1,
pathIndex: -1,
isPseudoRoot: true
});
if (result === RESULT_MISS) {
throw new Error('No node found at the given path.');
}
return result.children;
};
const removeNodeAtPath = ({
treeData,
path,
getNodeKey,
ignoreCollapsed = true
}) => {
return changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: undefined
});
};
const removeNode = ({
treeData,
path,
getNodeKey,
ignoreCollapsed = true
}) => {
let removedNode;
let removedTreeIndex;
const nextTreeData = changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: ({
node,
treeIndex
}) => {
removedNode = node;
removedTreeIndex = treeIndex;
return undefined;
}
});
return {
treeData: nextTreeData,
node: removedNode,
treeIndex: removedTreeIndex
};
};
const getNodeAtPath = ({
treeData,
path,
getNodeKey,
ignoreCollapsed = true
}) => {
let foundNodeInfo;
try {
changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: ({
node,
treeIndex
}) => {
foundNodeInfo = {
node,
treeIndex
};
return node;
}
});
} catch {}
return foundNodeInfo;
};
const addNodeUnderParent = ({
treeData,
newNode,
parentKey = undefined,
getNodeKey,
ignoreCollapsed = true,
expandParent = false,
addAsFirstChild = false
}) => {
if (parentKey === null || parentKey === undefined) {
return addAsFirstChild ? {
treeData: [newNode, ...(treeData || [])],
treeIndex: 0
} : {
treeData: [...(treeData || []), newNode],
treeIndex: (treeData || []).length
};
}
let insertedTreeIndex;
let hasBeenAdded = false;
const changedTreeData = map({
treeData,
getNodeKey,
ignoreCollapsed,
callback: ({
node,
treeIndex,
path
}) => {
const key = path ? path[path.length - 1] : undefined;
if (hasBeenAdded || key !== parentKey) {
return node;
}
hasBeenAdded = true;
const parentNode = {
...node
};
if (expandParent) {
parentNode.expanded = true;
}
if (!parentNode.children) {
insertedTreeIndex = treeIndex + 1;
return {
...parentNode,
children: [newNode]
};
}
if (typeof parentNode.children === 'function') {
throw new TypeError('Cannot add to children defined by a function');
}
let nextTreeIndex = treeIndex + 1;
for (let i = 0; i < parentNode.children.length; i += 1) {
nextTreeIndex += 1 + getDescendantCount({
node: parentNode.children[i],
ignoreCollapsed
});
}
insertedTreeIndex = nextTreeIndex;
const children = addAsFirstChild ? [newNode, ...parentNode.children] : [...parentNode.children, newNode];
return {
...parentNode,
children
};
}
});
if (!hasBeenAdded) {
throw new Error('No node found with the given key.');
}
return {
treeData: changedTreeData,
treeIndex: insertedTreeIndex
};
};
const addNodeAtDepthAndIndex = ({
targetDepth,
minimumTreeIndex,
newNode,
ignoreCollapsed,
expandParent,
isPseudoRoot = false,
isLastChild,
node,
currentIndex,
currentDepth,
getNodeKey,
path = []
}) => {
const selfPath = n => isPseudoRoot ? [] : [...path, getNodeKey({
node: n,
treeIndex: currentIndex
})];
if (currentIndex >= minimumTreeIndex - 1 || isLastChild && !(node.children && node.children.length > 0)) {
if (typeof node.children === 'function') {
throw new TypeError('Cannot add to children defined by a function');
} else {
const extraNodeProps = expandParent ? {
expanded: true
} : {};
const nextNode = {
...node,
...extraNodeProps,
children: node.children ? [newNode, ...node.children] : [newNode]
};
return {
node: nextNode,
nextIndex: currentIndex + 2,
insertedTreeIndex: currentIndex + 1,
parentPath: selfPath(nextNode),
parentNode: isPseudoRoot ? undefined : nextNode
};
}
}
if (currentDepth >= targetDepth - 1) {
if (!node.children || typeof node.children === 'function' || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) {
return {
node,
nextIndex: currentIndex + 1
};
}
let childIndex = currentIndex + 1;
let insertedTreeIndex;
let insertIndex;
for (let i = 0; i < node.children.length; i += 1) {
if (childIndex >= minimumTreeIndex) {
insertedTreeIndex = childIndex;
insertIndex = i;
break;
}
childIndex += 1 + getDescendantCount({
node: node.children[i],
ignoreCollapsed
});
}
if (insertIndex === null || insertIndex === undefined) {
if (childIndex < minimumTreeIndex && !isLastChild) {
return {
node,
nextIndex: childIndex
};
}
insertedTreeIndex = childIndex;
insertIndex = node.children.length;
}
const nextNode = {
...node,
children: [...node.children.slice(0, insertIndex), newNode, ...node.children.slice(insertIndex)]
};
return {
node: nextNode,
nextIndex: childIndex,
insertedTreeIndex,
parentPath: selfPath(nextNode),
parentNode: isPseudoRoot ? undefined : nextNode
};
}
if (!node.children || typeof node.children === 'function' || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) {
return {
node,
nextIndex: currentIndex + 1
};
}
let insertedTreeIndex;
let pathFragment;
let parentNode;
let childIndex = currentIndex + 1;
let newChildren = node.children;
if (typeof newChildren !== 'function') {
newChildren = newChildren.map((child, i) => {
if (insertedTreeIndex !== null && insertedTreeIndex !== undefined) {
return child;
}
const mapResult = addNodeAtDepthAndIndex({
targetDepth,
minimumTreeIndex,
newNode,
ignoreCollapsed,
expandParent,
isLastChild: isLastChild && i === newChildren.length - 1,
node: child,
currentIndex: childIndex,
currentDepth: currentDepth + 1,
getNodeKey,
path: []
});
if ('insertedTreeIndex' in mapResult) {
({
insertedTreeIndex,
parentNode,
parentPath: pathFragment
} = mapResult);
}
childIndex = mapResult.nextIndex;
return mapResult.node;
});
}
const nextNode = {
...node,
children: newChildren
};
const result = {
node: nextNode,
nextIndex: childIndex
};
if (insertedTreeIndex !== null && insertedTreeIndex !== undefined) {
result.insertedTreeIndex = insertedTreeIndex;
result.parentPath = [...selfPath(nextNode), ...pathFragment];
result.parentNode = parentNode;
}
return result;
};
const insertNode = ({
treeData,
depth: targetDepth,
minimumTreeIndex,
newNode,
getNodeKey,
ignoreCollapsed = true,
expandParent = false
}) => {
if (!treeData && targetDepth === 0) {
return {
treeData: [newNode],
treeIndex: 0,
path: [getNodeKey({
node: newNode,
treeIndex: 0
})],
parentNode: undefined
};
}
const insertResult = addNodeAtDepthAndIndex({
targetDepth,
minimumTreeIndex,
newNode,
ignoreCollapsed,
expandParent,
getNodeKey,
isPseudoRoot: true,
isLastChild: true,
node: {
children: treeData
},
currentIndex: -1,
currentDepth: -1
});
if (!('insertedTreeIndex' in insertResult)) {
throw new Error('No suitable position found to insert.');
}
const treeIndex = insertResult.insertedTreeIndex;
return {
treeData: insertResult.node.children,
treeIndex,
path: [...insertResult.parentPath, getNodeKey({
node: newNode,
treeIndex
})],
parentNode: insertResult.parentNode
};
};
const getFlatDataFromTree = ({
treeData,
getNodeKey,
ignoreCollapsed = true
}) => {
if (!treeData || treeData.length === 0) {
return [];
}
const flattened = [];
walk({
treeData,
getNodeKey,
ignoreCollapsed,
callback: nodeInfo => {
flattened.push(nodeInfo);
}
});
return flattened;
};
const getTreeFromFlatData = ({
flatData,
getKey = node => node.id,
getParentKey = node => node.parentId,
rootKey = '0'
}) => {
if (!flatData) {
return [];
}
const childrenToParents = {};
for (const child of flatData) {
const parentKey = getParentKey(child);
if (parentKey in childrenToParents) {
childrenToParents[parentKey].push(child);
} else {
childrenToParents[parentKey] = [child];
}
}
if (!(rootKey in childrenToParents)) {
return [];
}
const trav = parent => {
const parentKey = getKey(parent);
if (parentKey in childrenToParents) {
return {
...parent,
children: childrenToParents[parentKey].map(child => trav(child))
};
}
return {
...parent
};
};
return childrenToParents[rootKey].map(child => trav(child));
};
const isDescendant = (older, younger) => {
return !!older.children && typeof older.children !== 'function' && older.children.some(child => child === younger || isDescendant(child, younger));
};
const getDepth = (node, depth = 0) => {
if (!node.children) {
return depth;
}
if (typeof node.children === 'function') {
return depth + 1;
}
return node.children.reduce((deepest, child) => Math.max(deepest, getDepth(child, depth + 1)), depth);
};
const find = ({
getNodeKey,
treeData,
searchQuery,
searchMethod,
searchFocusOffset,
expandAllMatchPaths = false,
expandFocusMatchPaths = true
}) => {
let matchCount = 0;
const trav = ({
isPseudoRoot = false,
node,
currentIndex,
path = []
}) => {
let matches = [];
let isSelfMatch = false;
let hasFocusMatch = false;
const selfPath = isPseudoRoot ? [] : [...path, getNodeKey({
node,
treeIndex: currentIndex
})];
const extraInfo = isPseudoRoot ? undefined : {
path: selfPath,
treeIndex: currentIndex
};
const hasChildren = node.children && typeof node.children !== 'function' && node.children.length > 0;
if (!isPseudoRoot && searchMethod({
...extraInfo,
node,
searchQuery
})) {
if (matchCount === searchFocusOffset) {
hasFocusMatch = true;
}
matchCount += 1;
isSelfMatch = true;
}
let childIndex = currentIndex;
const newNode = {
...node
};
if (hasChildren) {
newNode.children = newNode.children.map(child => {
const mapResult = trav({
node: child,
currentIndex: childIndex + 1,
path: selfPath
});
if (mapResult.node.expanded) {
childIndex = mapResult.treeIndex;
} else {
childIndex += 1;
}
if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) {
matches = [...matches, ...mapResult.matches];
if (mapResult.hasFocusMatch) {
hasFocusMatch = true;
}
if (expandAllMatchPaths && mapResult.matches.length > 0 || (expandAllMatchPaths || expandFocusMatchPaths) && mapResult.hasFocusMatch) {
newNode.expanded = true;
}
}
return mapResult.node;
});
}
if (!isPseudoRoot && !newNode.expanded) {
matches = matches.map(match => ({
...match,
treeIndex: undefined
}));
}
if (isSelfMatch) {
matches = [{
...extraInfo,
node: newNode
}, ...matches];
}
return {
node: matches.length > 0 ? newNode : node,
matches,
hasFocusMatch,
treeIndex: childIndex
};
};
const result = trav({
node: {
children: treeData
},
isPseudoRoot: true,
currentIndex: -1
});
return {
matches: result.matches,
treeData: result.node.children
};
};
const classnames = (...classes) => classes.filter(Boolean).join(' ');
const defaultProps$3 = {
isSearchMatch: false,
isSearchFocus: false,
canDrag: false,
toggleChildrenVisibility: undefined,
buttons: [],
className: '',
style: {},
parentNode: undefined,
draggedNode: undefined,
canDrop: false,
title: undefined,
subtitle: undefined,
rowDirection: 'ltr'
};
const NodeRendererDefault = function (props) {
props = {
...defaultProps$3,
...props
};
const {
scaffoldBlockPxWidth,
toggleChildrenVisibility,
connectDragPreview,
connectDragSource,
isDragging,
canDrop,
canDrag,
node,
title,
subtitle,
draggedNode,
path,
treeIndex,
isSearchMatch,
isSearchFocus,
buttons,
className,
style,
didDrop,
treeId: _treeId,
isOver: _isOver,
parentNode: _parentNode,
rowDirection,
...otherProps
} = props;
const nodeTitle = title || node.title;
const nodeSubtitle = subtitle || node.subtitle;
const rowDirectionClass = rowDirection === 'rtl' ? 'rst__rtl' : undefined;
let handle;
if (canDrag) {
handle = typeof node.children === 'function' && node.expanded ? jsxRuntime.jsx("div", {
className: "rst__loadingHandle",
children: jsxRuntime.jsx("div", {
className: "rst__loadingCircle",
children: Array.from({
length: 12
}).map((_, index) => jsxRuntime.jsx("div", {
className: classnames('rst__loadingCirclePoint', rowDirectionClass ?? '')
}, index))
})
}) : connectDragSource(jsxRuntime.jsx("div", {
className: "rst__moveHandle"
}), {
dropEffect: 'copy'
});
}
const isDraggedDescendant = draggedNode && isDescendant(draggedNode, node);
const isLandingPadActive = !didDrop && isDragging;
let buttonStyle = {
left: -0.5 * scaffoldBlockPxWidth,
right: 0
};
if (rowDirection === 'rtl') {
buttonStyle = {
right: -0.5 * scaffoldBlockPxWidth,
left: 0
};
}
return jsxRuntime.jsxs("div", {
style: {
height: '100%'
},
...otherProps,
children: [toggleChildrenVisibility && node.children && (node.children.length > 0 || typeof node.children === 'function') && jsxRuntime.jsxs("div", {
children: [jsxRuntime.jsx("button", {
type: "button",
"aria-label": node.expanded ? 'Collapse' : 'Expand',
className: classnames(node.expanded ? 'rst__collapseButton' : 'rst__expandButton', rowDirectionClass ?? ''),
style: buttonStyle,
onClick: () => toggleChildrenVisibility({
node,
path,
treeIndex
})
}), node.expanded && !isDragging && jsxRuntime.jsx("div", {
style: {
width: scaffoldBlockPxWidth
},
className: classnames('rst__lineChildren', rowDirectionClass ?? '')
})]
}), jsxRuntime.jsx("div", {
className: classnames('rst__rowWrapper', rowDirectionClass ?? ''),
children: connectDragPreview(jsxRuntime.jsxs("div", {
className: classnames('rst__row', isLandingPadActive ? 'rst__rowLandingPad' : '', isLandingPadActive && !canDrop ? 'rst__rowCancelPad' : '', isSearchMatch ? 'rst__rowSearchMatch' : '', isSearchFocus ? 'rst__rowSearchFocus' : '', rowDirectionClass ?? '', className ?? ''),
style: {
opacity: isDraggedDescendant ? 0.5 : 1,
...style
},
children: [handle, jsxRuntime.jsxs("div", {
className: classnames('rst__rowContents', canDrag ? '' : 'rst__rowContentsDragDisabled', rowDirectionClass ?? ''),
children: [jsxRuntime.jsxs("div", {
className: classnames('rst__rowLabel', rowDirectionClass ?? ''),
children: [jsxRuntime.jsx("span", {
className: classnames('rst__rowTitle', node.subtitle ? 'rst__rowTitleWithSubtitle' : ''),
children: typeof nodeTitle === 'function' ? nodeTitle({
node,
path,
treeIndex
}) : nodeTitle
}), nodeSubtitle && jsxRuntime.jsx("span", {
className: "rst__rowSubtitle",
children: typeof nodeSubtitle === 'function' ? nodeSubtitle({
node,
path,
treeIndex
}) : nodeSubtitle
})]
}), jsxRuntime.jsx("div", {
className: "rst__rowToolbar",
children: buttons?.map((btn, index) => jsxRuntime.jsx("div", {
className: "rst__toolbarButton",
children: btn
}, index))
})]
})]
}))
})]
});
};
const defaultProps$2 = {
isOver: false,
canDrop: false
};
const PlaceholderRendererDefault = function (props) {
props = {
...defaultProps$2,
...props
};
const {
canDrop,
isOver
} = props;
return jsxRuntime.jsx("div", {
className: classnames('rst__placeholder', canDrop ? 'rst__placeholderLandingPad' : '', canDrop && !isOver ? 'rst__placeholderCancelPad' : '')
});
};
const defaultProps$1 = {
swapFrom: undefined,
swapDepth: undefined,
swapLength: undefined,
canDrop: false,
draggedNode: undefined,
rowDirection: 'ltr'
};
class TreeNodeComponent extends React.Component {
render() {
const props = {
...defaultProps$1,
...this.props
};
const {
children,
listIndex,
swapFrom,
swapLength,
swapDepth,
scaffoldBlockPxWidth,
lowerSiblingCounts,
connectDropTarget,
isOver,
draggedNode,
canDrop,
treeIndex,
rowHeight,
treeId: _treeId,
getPrevRow: _getPrevRow,
node: _node,
path: _path,
rowDirection,
...otherProps
} = props;
const rowDirectionClass = rowDirection === 'rtl' ? 'rst__rtl' : undefined;
const scaffoldBlockCount = lowerSiblingCounts.length;
const scaffold = [];
for (const [i, lowerSiblingCount] of lowerSiblingCounts.entries()) {
let lineClass = '';
if (lowerSiblingCount > 0) {
if (listIndex === 0) {
lineClass = 'rst__lineHalfHorizontalRight rst__lineHalfVerticalBottom';
} else if (i === scaffoldBlockCount - 1) {
lineClass = 'rst__lineHalfHorizontalRight rst__lineFullVertical';
} else {
lineClass = 'rst__lineFullVertical';
}
} else if (listIndex === 0) {
lineClass = 'rst__lineHalfHorizontalRight';
} else if (i === scaffoldBlockCount - 1) {
lineClass = 'rst__lineHalfVerticalTop rst__lineHalfHorizontalRight';
}
scaffold.push(jsxRuntime.jsx("div", {
style: {
width: scaffoldBlockPxWidth
},
className: classnames('rst__lineBlock', lineClass, rowDirectionClass ?? '')
}, `pre_${1 + i}`));
if (treeIndex !== listIndex && i === swapDepth) {
let highlightLineClass = '';
if (listIndex === swapFrom + swapLength - 1) {
highlightLineClass = 'rst__highlightBottomLeftCorner';
} else if (treeIndex === swapFrom) {
highlightLineClass = 'rst__highlightTopLeftCorner';
} else {
highlightLineClass = 'rst__highlightLineVertical';
}
const style = rowDirection === 'rtl' ? {
width: scaffoldBlockPxWidth,
right: scaffoldBlockPxWidth * i
} : {
width: scaffoldBlockPxWidth,
left: scaffoldBlockPxWidth * i
};
scaffold.push(jsxRuntime.jsx("div", {
style: style,
className: classnames('rst__absoluteLineBlock', highlightLineClass, rowDirectionClass ?? '')
}, i));
}
}
const style = rowDirection === 'rtl' ? {
right: scaffoldBlockPxWidth * scaffoldBlockCount
} : {
left: scaffoldBlockPxWidth * scaffoldBlockCount
};
let calculatedRowHeight = rowHeight;
if (typeof rowHeight === 'function') {
calculatedRowHeight = rowHeight(treeIndex, _node, _path);
}
return connectDropTarget(jsxRuntime.jsxs("div", {
...otherProps,
style: {
height: `${calculatedRowHeight}px`
},
className: classnames('rst__node', rowDirectionClass ?? ''),
ref: node => this.node = node,
children: [scaffold, jsxRuntime.jsx("div", {
className: "rst__nodeContent",
style: style,
children: React.Children.map(children, child => React.cloneElement(child, {
isOver,
canDrop,
draggedNode
}))
})]
}));
}
}
const defaultProps = {
canDrop: false,
draggedNode: undefined
};
const TreePlaceholder = props => {
props = {
...defaultProps,
...props
};
const {
children,
connectDropTarget,
treeId,
drop,
...otherProps
} = props;
return connectDropTarget(jsxRuntime.jsx("div", {
children: React.Children.map(children, child => React.cloneElement(child, {
...otherProps
}))
}));
};
let rafId = 0;
const nodeDragSourcePropInjection = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
didDrop: monitor.didDrop()
});
const wrapSource = (el, startDrag, endDrag, dndType) => {
const nodeDragSource = {
beginDrag: props => {
startDrag(props);
return {
node: props.node,
parentNode: props.parentNode,
path: props.path,
treeIndex: props.treeIndex,
treeId: props.treeId
};
},
endDrag: (props, monitor) => {
endDrag(monitor.getDropResult());
},
isDragging: (props, monitor) => {
const dropTargetNode = monitor.getItem().node;
const draggedNode = props.node;
return draggedNode === dropTargetNode;
}
};
return reactDnd.DragSource(dndType, nodeDragSource, nodeDragSourcePropInjection)(el);
};
const propInjection = (connect, monitor) => {
const dragged = monitor.getItem();
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedNode: dragged ? dragged.node : undefined
};
};
const wrapPlaceholder = (el, treeId, drop, dndType) => {
const placeholderDropTarget = {
drop: (dropTargetProps, monitor) => {
const {
node,
path,
treeIndex
} = monitor.getItem();
const result = {
node,
path,
treeIndex,
treeId,
minimumTreeIndex: 0,
depth: 0
};
drop(result);
return result;
}
};
return reactDnd.DropTarget(dndType, placeholderDropTarget, propInjection)(el);
};
const getTargetDepth = (dropTargetProps, monitor, component, canNodeHaveChildren, treeId, maxDepth) => {
let dropTargetDepth = 0;
const rowAbove = dropTargetProps.getPrevRow();
if (rowAbove) {
const {
node
} = rowAbove;
let {
path
} = rowAbove;
const aboveNodeCannotHaveChildren = !canNodeHaveChildren(node);
if (aboveNodeCannotHaveChildren) {
path = path.slice(0, -1);
}
dropTargetDepth = Math.min(path.length, dropTargetProps.path.length);
}
let blocksOffset;
let dragSourceInitialDepth = (monitor.getItem().path || []).length;
if (monitor.getItem().treeId === treeId) {
const direction = dropTargetProps.rowDirection === 'rtl' ? -1 : 1;
blocksOffset = Math.round(direction * monitor.getDifferenceFromInitialOffset().x / dropTargetProps.scaffoldBlockPxWidth);
} else {
dragSourceInitialDepth = 0;
if (component) {
const relativePosition = component.node.getBoundingClientRect();
const leftShift = monitor.getSourceClientOffset().x - relativePosition.left;
blocksOffset = Math.round(leftShift / dropTargetProps.scaffoldBlockPxWidth);
} else {
blocksOffset = dropTargetProps.path.length;
}
}
let targetDepth = Math.min(dropTargetDepth, Math.max(0, dragSourceInitialDepth + blocksOffset - 1));
if (maxDepth !== undefined && maxDepth !== undefined) {
const draggedNode = monitor.getItem().node;
const draggedChildDepth = getDepth(draggedNode);
targetDepth = Math.max(0, Math.min(targetDepth, maxDepth - draggedChildDepth - 1));
}
return targetDepth;
};
const canDrop = (dropTargetProps, monitor, canNodeHaveChildren, treeId, maxDepth, treeRefcanDrop, draggingTreeData, treeReftreeData, getNodeKey) => {
if (!monitor.isOver()) {
return false;
}
const rowAbove = dropTargetProps.getPrevRow();
const abovePath = rowAbove ? rowAbove.path : [];
const aboveNode = rowAbove ? rowAbove.node : {};
const targetDepth = getTargetDepth(dropTargetProps, monitor, undefined, canNodeHaveChildren, treeId, maxDepth);
if (targetDepth >= abovePath.length && typeof aboveNode.children === 'function') {
return false;
}
if (typeof treeRefcanDrop === 'function') {
const {
node
} = monitor.getItem();
return treeRefcanDrop({
node,
prevPath: monitor.getItem().path,
prevParent: monitor.getItem().parentNode,
prevTreeIndex: monitor.getItem().treeIndex,
nextPath: dropTargetProps.children.props.path,
nextParent: dropTargetProps.children.props.parentNode,
nextTreeIndex: dropTargetProps.children.props.treeIndex
});
}
return true;
};
const wrapTarget = (el, canNodeHaveChildren, treeId, maxDepth, treeRefcanDrop, drop, dragHover, dndType, draggingTreeData, treeReftreeData, getNodeKey) => {
const nodeDropTarget = {
drop: (dropTargetProps, monitor, component) => {
const result = {
node: monitor.getItem().node,
path: monitor.getItem().path,
treeIndex: monitor.getItem().treeIndex,
treeId,
minimumTreeIndex: dropTargetProps.treeIndex,
depth: getTargetDepth(dropTargetProps, monitor, component, canNodeHaveChildren, treeId, maxDepth)
};
drop(result);
return result;
},
hover: (dropTargetProps, monitor, component) => {
const targetDepth = getTargetDepth(dropTargetProps, monitor, component, canNodeHaveChildren, treeId, maxDepth);
const draggedNode = monitor.getItem().node;
const needsRedraw = dropTargetProps.node !== draggedNode || targetDepth !== dropTargetProps.path.length - 1;
if (!needsRedraw) {
return;
}
cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
const item = monitor.getItem();
if (!item || !monitor.isOver()) {
return;
}
dragHover({
node: draggedNode,
path: item.path,
minimumTreeIndex: dropTargetProps.listIndex,
depth: targetDepth
});
});
},
canDrop: (dropTargetProps, monitor) => canDrop(dropTargetProps, monitor, canNodeHaveChildren, treeId, maxDepth, treeRefcanDrop)
};
return reactDnd.DropTarget(dndType, nodeDropTarget, propInjection)(el);
};
const slideRows = (rows, fromIndex, toIndex, count = 1) => {
const rowsWithoutMoved = [...rows.slice(0, fromIndex), ...rows.slice(fromIndex + count)];
return [...rowsWithoutMoved.slice(0, toIndex), ...rows.slice(fromIndex, fromIndex + count), ...rowsWithoutMoved.slice(toIndex)];
};
const memoize = f => {
let savedArgsArray = [];
let savedKeysArray = [];
let savedResult;
return args => {
const keysArray = Object.keys(args).sort();
const argsArray = keysArray.map(key => args[key]);
if (argsArray.length !== savedArgsArray.length || argsArray.some((arg, index) => arg !== savedArgsArray[index]) || keysArray.some((key, index) => key !== savedKeysArray[index])) {
savedArgsArray = argsArray;
savedKeysArray = keysArray;
savedResult = f(args);
}
return savedResult;
};
};
const memoizedInsertNode = memoize(insertNode);
const memoizedGetFlatDataFromTree = memoize(getFlatDataFromTree);
const memoizedGetDescendantCount = memoize(getDescendantCount);
let treeIdCounter = 1;
const mergeTheme = props => {
const merged = {
...props,
style: {
...props.theme.style,
...props.style
},
innerStyle: {
...props.theme.innerStyle,
...props.innerStyle
}
};
const overridableDefaults = {
nodeContentRenderer: NodeRendererDefault,
placeholderRenderer: PlaceholderRendererDefault,
scaffoldBlockPxWidth: 44,
slideRegionSize: 100,
rowHeight: 62,
treeNodeRenderer: TreeNodeComponent
};
for (const propKey of Object.keys(overridableDefaults)) {
if (props[propKey] === undefined) {
merged[propKey] = props.theme[propKey] === undefined ? overridableDefaults[propKey] : props.theme[propKey];
}
}
return merged;
};
class ReactSortableTree extends React.Component {
static search(props, state, seekIndex, expand, singleSearch) {
const {
onChange,
getNodeKey,
searchFinishCallback,
searchQuery,
searchMethod,
searchFocusOffset,
onlyExpandSearchedNodes
} = props;
const {
instanceProps
} = state;
if (!searchQuery && !searchMethod) {
if (searchFinishCallback) {
searchFinishCallback([]);
}
return {
searchMatches: []
};
}
const newState = {
instanceProps: {}
};
const {
treeData: expandedTreeData,
matches: searchMatches
} = find({
getNodeKey,
treeData: onlyExpandSearchedNodes ? toggleExpandedForAll({
treeData: instanceProps.treeData,
expanded: false
}) : instanceProps.treeData,
searchQuery,
searchMethod: searchMethod || defaultSearchMethod,
searchFocusOffset,
expandAllMatchPaths: expand && !singleSearch,
expandFocusMatchPaths: !!expand
});
if (expand) {
newState.instanceProps.ignoreOneTreeUpdate = true;
onChange(expandedTreeData);
}
if (searchFinishCallback) {
searchFinishCallback(searchMatches);
}
let searchFocusTreeIndex;
if (seekIndex && searchFocusOffset !== undefined && searchFocusOffset < searchMatches.length) {
searchFocusTreeIndex = searchMatches[searchFocusOffset].treeIndex;
}
newState.searchMatches = searchMatches;
newState.searchFocusTreeIndex = searchFocusTreeIndex;
return newState;
}
static loadLazyChildren(props, state) {
const {
instanceProps
} = state;
walk({
treeData: instanceProps.treeData,
getNodeKey: props.getNodeKey,
callback: ({
node,
path,
lowerSiblingCounts,
treeIndex
}) => {
if (node.children && typeof node.children === 'function' && (node.expanded || props.loadCollapsedLazyChildren)) {
node.children({
node,
path,
lowerSiblingCounts,
treeIndex,
done: childrenArray => props.onChange(changeNodeAtPath({
treeData: instanceProps.treeData,
path,
newNode: ({
node: oldNode
}) => oldNode === node ? {
...oldNode,
children: childrenArray
} : oldNode,
getNodeKey: props.getNodeKey
}))
});
}
}
});
}
constructor(props) {
super(props);
this.listRef = props.virtuosoRef || React.createRef();
this.listProps = props.virtuosoProps || {};
const {
dndType,
nodeContentRenderer,
treeNodeRenderer,
slideRegionSize
} = mergeTheme(props);
this.treeId = `rst__${treeIdCounter}`;
treeIdCounter += 1;
this.dndType = dndType || this.treeId;
this.nodeContentRenderer = wrapSource(nodeContentRenderer, this.startDrag, this.endDrag, this.dndType);
this.treePlaceholderRenderer = wrapPlaceholder(TreePlaceholder, this.treeId, this.drop, this.dndType);
this.scrollZoneVirtualList = (withScrolling.createScrollingComponent || withScrolling)(React.forwardRef((props, ref) => {
const {
dragDropManager,
rowHeight,
...otherProps
} = props;
return jsxRuntime.jsx(reactVirtuoso.Virtuoso, {
ref: this.listRef,
scrollerRef: scrollContainer => ref.current = scrollContainer,
...otherProps
});
}));
this.vStrength = withScrolling.createVerticalStrength(slideRegionSize);
this.hStrength = withScrolling.createHorizontalStrength(slideRegionSize);
this.state = {
draggingTreeData: undefined,
draggedNode: undefined,
draggedMinimumTreeIndex: undefined,
draggedDepth: undefined,
searchMatches: [],
searchFocusTreeIndex: undefined,
dragging: false,
instanceProps: {
treeData: [],
ignoreOneTreeUpdate: false,
searchQuery: undefined,
searchFocusOffset: undefined
}
};
this.treeNodeRenderer = wrapTarget(treeNodeRenderer, this.canNodeHaveChildren, this.treeId, this.props.maxDepth, this.props.canDrop, this.drop, this.dragHover, this.dndType, this.state.draggingTreeData, this.props.treeData, this.props.getNodeKey);
this.toggleChildrenVisibility = this.toggleChildrenVisibility.bind(this);
this.moveNode = this.moveNode.bind(this);
this.startDrag = this.startDrag.bind(this);
this.dragHover = this.dragHover.bind(this);
this.endDrag = this.endDrag.bind(this);
this.drop = this.drop.bind(this);
this.handleDndMonitorChange = this.handleDndMonitorChange.bind(this);
}
componentDidMount() {
ReactSortableTree.loadLazyChildren(this.props, this.state);
const stateUpdate = ReactSortableTree.search(this.props, this.state, true, true, false);
this.setState(stateUpdate);
this.clearMonitorSubscription = this.props.dragDropManager.getMonitor().subscribeToStateChange(this.handleDndMonitorChange);
}
static getDerivedStateFromProps(nextProps, prevState) {
const {
instanceProps
} = prevState;
const newState = {};
const newInstanceProps = {
...instanceProps
};
const isTreeDataEqual = isEqual(instanceProps.treeData, nextProps.treeData);
newInstanceProps.treeData = nextProps.treeData;
if (!isTreeDataEqual) {
if (instanceProps.ignoreOneTreeUpdate) {
newInstanceProps.ignoreOneTreeUpdate = false;
} else {
newState.searchFocusTreeIndex = undefined;
ReactSortableTree.loadLazyChildren(nextProps, prevState);
Object.assign(newState, ReactSortableTree.search(nextProps, prevState, false, false, false));
}
newState.draggingTreeData = undefined;
newState.draggedNode = undefined;
newState.draggedMinimumTreeIndex = undefined;
newState.draggedDepth = undefined;
newState.dragging = false;
} else if (!isEqual(instanceProps.searchQuery, nextProps.searchQuery)) {
Object.assign(newState, ReactSortableTree.search(nextProps, prevState, true, true, false));
} else if (instanceProps.searchFocusOffset !== nextProps.searchFocusOffset) {
Object.assign(newState, ReactSortableTree.search(nextProps, prevState, true, true, true));
}
newInstanceProps.searchQuery = nextProps.searchQuery;
newInstanceProps.searchFocusOffset = nextProps.searchFocusOffset;
newState.instanceProps = {
...newInstanceProps,
...newState.instanceProps
};
return newState;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.dragging !== prevState.dragging && this.props.onDragStateChanged) {
this.props.onDragStateChanged({
isDragging: this.state.dragging,
draggedNode: this.state.draggedNode
});
}
}
componentWillUnmount() {
this.clearMonitorSubscription();
}
handleDndMonitorChange() {
const monitor = this.props.dragDropManager.getMonitor();
if (!monitor.isDragging() && this.state.draggingTreeData) {
setTimeout(() => {
this.endDrag();
});
}
}
getRows(treeData) {
return memoizedGetFlatDataFromTree({
ignoreCollapsed: true,
getNodeKey: this.props.getNodeKey,
treeData
});
}
startDrag = ({
path
}) => {
this.setState(prevState => {
const {
treeData: draggingTreeData,
node: draggedNode,
treeIndex: draggedMinimumTreeIndex
} = removeNode({
treeData: prevState.instanceProps.treeData,
path,
getNodeKey: this.props.getNodeKey
});
return {
draggingTreeData,
draggedNode,
draggedDepth: path.length - 1,
draggedMinimumTreeIndex,
dragging: true
};
});
};
dragHover = ({
node: draggedNode,
depth: draggedDepth,
minimumTreeIndex: draggedMinimumTreeIndex
}) => {
if (this.state.draggedDepth === draggedDepth && this.state.draggedMinimumTreeIndex === draggedMinimumTreeIndex) {
return;
}
this.setState(({
draggingTreeData,
instanceProps
}) => {
const newDraggingTreeData = draggingTreeData || instanceProps.treeData;
const addedResult = memoizedInsertNode({
treeData: newDraggingTreeData,
newNode: draggedNode,
depth: draggedDepth,
minimumTreeIndex: draggedMinimumTreeIndex,
expandParent: true,
getNodeKey: this.props.getNodeKey
});
const rows = this.getRows(addedResult.treeData);
const expandedParentPath = rows[addedResult.treeIndex].path;
return {
draggedNode,
draggedDepth,
draggedMinimumTreeIndex,
draggingTreeData: changeNodeAtPath({
treeData: newDraggingTreeData,
path: expandedParentPath.slice(0, -1),
newNode: ({
node
}) => ({
...node,
expanded: true
}),
getNodeKey: this.props.getNodeKey
}),
searchFocusTreeIndex: undefined,
dragging: true
};
});
};
endDrag = dropResult => {
const {
instanceProps
} = this.state;
if (!dropResult) {
this.setState({
draggingTreeData: undefined,
draggedNode: undefined,
draggedMinimumTreeIndex: undefined,
draggedDepth: undefined,
dragging: false
});
} else if (dropResult.treeId !== this.treeId) {
const {
node,
path,
treeIndex
} = dropResult;
let shouldCopy = this.props.shouldCopyOnOutsideDrop;
if (typeof shouldCopy === 'function') {
shouldCopy = shouldCopy({
node,
prevTreeIndex: treeIndex,
prevPath: path
});
}
let treeData = this.state.draggingTreeData || instanceProps.treeData;
if (shouldCopy) {
treeData = changeNodeAtPath({
treeData: instanceProps.treeData,
path,
newNode: ({
node: copyNode
}) => ({
...copyNode
}),
getNodeKey: this.props.getNodeKey
});
}
this.props.onChange(treeData);
this.props.onMoveNode({
treeData,
node,
treeIndex: undefined,
path: undefined,
nextPath: undefined,
nextTreeIndex: undefined,
prevPath: path,
prevTreeIndex: treeIndex
});
}
};
drop = dropResult => {
this.moveNode(dropResult);
};
canNodeHaveChildren = node => {
const {
canNodeHaveChildren
} = this.props;
if (canNodeHaveChildren) {
return canNodeHaveChildren(node);
}
return true;
};
toggleChildrenVisibility({
node: targetNode,
path
}) {
const {
instanceProps
} = this.state;
const treeData = changeNodeAtPath({
treeData: instanceProps.treeData,
path,
newNode: ({
node
}) => ({
...node,
expanded: !node.expanded
}),
getNodeKey: this.props.getNodeKey
});
this.props.onChange(treeData);
this.props.onVisibilityToggle({
treeData,
node: targetNode,
expanded: !targetNode.expanded,
path
});
}
moveNode({
node,
path: prevPath,
treeIndex: prevTreeIndex,
depth,
minimumTreeIndex
}) {
const {
treeData,
treeIndex,
path,
parentNode: nextParentNode
} = insertNode({
treeData: this.state.draggingTreeData,
newNode: node,
depth,
minimumTreeIndex,
expandParent: true,
getNodeKey: this.props.getNodeKey
});
this.props.onChange(treeData);
this.props.onMoveNode({
treeData,
node,
treeIndex,
path,
nextPath: path,
nextTreeIndex: treeIndex,
prevPath,
prevTreeIndex,
nextParentNode
});
}
renderRow(row, {
listIndex,
style,
getPrevRow,
matchKeys,
swapFrom,
swapDepth,
swapLength
}) {
const {
node,
parentNode,
path,
lowerSiblingCounts,
treeIndex
} = row;
const {
canDrag,
generateNodeProps,
scaffoldBlockPxWidth,
searchFocusOffset,
rowDirection,
rowHeight
} = mergeTheme(this.props);
const TreeNodeRenderer = this.treeNodeRe