@wix/design-system
Version:
@wix/design-system
159 lines • 7.79 kB
JavaScript
import React from 'react';
import { addToTree, generateUniqueGroupId, getDropParent, removeFromTree, } from './utils';
import CustomDragLayer from './DragLayer';
import Container from './Container';
import { NestableListBaseContext } from './NestableListBaseContext';
import withDNDContext from './withDNDContext';
import { ZIndex } from '../common/ZIndex';
function replaceNegativeIndex(items, nextPosition, childrenProperty) {
let currItems = items;
return nextPosition.map(nextIndex => {
if (nextIndex !== -1) {
currItems = currItems[nextIndex][childrenProperty] || [];
return nextIndex;
}
return currItems.length;
});
}
// fixing a bug where the new path is increased in nesting
// and the first position jumps too far by 2, and that's why it decreased by 1
// need to check if the jump can become more severe
function getRealNextPosition(prev, next) {
// moving up a level
if (prev.length < next.length) {
return next.map((nextIndex, i) => {
if (typeof prev[i] !== 'number') {
return nextIndex;
}
return nextIndex > prev[i] ? nextIndex - 1 : nextIndex;
});
}
return next;
}
class NestableListBase extends React.PureComponent {
constructor() {
super(...arguments);
this.state = {
items: this.props.items,
dragging: false,
};
// drag and drop between multiple nestable lists is not supported
// according to prevent it for now was created groupName - unique value for every nestable list
// somethings similar exists in SortableListBase component.
this.groupName = generateUniqueGroupId();
// FIXME: reference to latest `state.items`. This is a workaround for issue
// with `moveItem` where it updates `state.items` and if the update occurs
// multiple times before the component re-renders, the `state.items` will be
// be updated based on the previous state, not the latest one, which in some
// cases leads to item duplication or removal.
// This should be solved by not referring to current state by `this.state` and
// instead providing callback to `setState` that receives the latest state.
// However, due to architectural decisions this is not possible without
// rewriting the whole component as `moveItem` must return new position of the
// dragged item which is then used by the item itself to update its state.
this._items = this.props.items;
this.moveItem = ({ dragItem, prevPosition, nextPosition }) => {
const { childrenProperty, preventChangeDepth, preventChangeParent, enforcePinnedOrder, items, } = this.props;
if (dragItem.preventItemChangeDepth &&
nextPosition.length !== prevPosition.length) {
return prevPosition;
}
if (preventChangeDepth && nextPosition.length > 1) {
const parent = getDropParent(items, nextPosition, childrenProperty);
if (!parent) {
return prevPosition;
}
}
if (preventChangeParent) {
const prevParentPath = prevPosition.slice(0, -1);
const nextParentPath = nextPosition.slice(0, -1);
const prevParent = getDropParent(items, prevParentPath, childrenProperty);
const nextParent = getDropParent(items, nextParentPath, childrenProperty);
if (prevParent?.id !== nextParent?.id) {
return prevPosition;
}
}
let newItems = this._items;
// the remove action might affect the next position,
// so update next coordinates accordingly
let realNextPosition = getRealNextPosition(prevPosition, nextPosition);
if (enforcePinnedOrder) {
const shouldProhibitPinMoveDown = !!dragItem.isPinned && !realNextPosition.isPinned;
const shouldProhibitNonPinMoveUp = !dragItem.isPinned && !!realNextPosition.isPinned;
if (shouldProhibitPinMoveDown && shouldProhibitNonPinMoveUp) {
return prevPosition;
}
}
if (realNextPosition[realNextPosition.length - 1] === -1) {
realNextPosition = replaceNegativeIndex(newItems, realNextPosition, childrenProperty);
}
newItems = removeFromTree(newItems, prevPosition, childrenProperty);
newItems = addToTree(newItems, dragItem, realNextPosition, childrenProperty);
this._items = newItems;
this.setState({ items: newItems });
return realNextPosition;
};
this.dropItem = item => {
this.props.onUpdate?.({ items: this.state.items, item });
};
this.onDragStart = itemProps => {
this.setState({ dragging: true });
this.props.onDragStart?.(itemProps);
};
this.onDragEnd = itemProps => {
this.setState({ dragging: false });
this.props.onDragEnd?.(itemProps);
};
}
// tried to use getDerivedStateFromProps but encounter an issue where the state was
// updated internally but props items stayed the same and it caused the new state to be
// overridden with the old state
// can be done if component is controlled but requires refactor
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.items !== this.state.items) {
this.setState({ items: newProps.items });
this._items = newProps.items;
}
}
render() {
const { items } = this.state;
const { renderItem, readOnly, childrenProperty, childrenStyle, renderAction, isRenderDraggingChildren, useDragHandle, maxDepth, preventChangeDepth, preventChangeParent, enforcePinnedOrder, threshold, theme, renderPrefix, dragLayerZIndex, zIndex, } = this.props;
return (React.createElement(NestableListBaseContext.Provider, { value: {
groupName: this.groupName,
useDragHandle,
maxDepth,
preventChangeDepth,
preventChangeParent,
enforcePinnedOrder,
threshold,
readOnly,
renderItem,
renderPrefix,
moveItem: this.moveItem,
dropItem: this.dropItem,
onDragStart: this.onDragStart,
onDragEnd: this.onDragEnd,
} },
React.createElement("div", null,
React.createElement(Container, { treeDepth: 1, items: items, renderAction: renderAction, parentPosition: [], childrenProperty: childrenProperty, childrenStyle: childrenStyle, isRenderDraggingChildren: isRenderDraggingChildren, topLevel: true, theme: theme }),
this.state.dragging && (React.createElement(CustomDragLayer, { siblings: items, isRenderDraggingChildren: isRenderDraggingChildren, renderItem: renderItem, childrenProperty: childrenProperty, childrenStyle: childrenStyle, theme: theme, dragLayerZIndex: zIndex ?? dragLayerZIndex })))));
}
}
NestableListBase.defaultProps = {
items: [],
isRenderDraggingChildren: false,
childrenProperty: 'children',
childrenStyle: {},
renderPrefix: () => null,
renderAction: () => null,
onUpdate: () => { },
useDragHandle: false,
preventChangeDepth: false,
preventChangeParent: false,
enforcePinnedOrder: false,
maxDepth: Infinity,
threshold: 30,
dragLayerZIndex: ZIndex.nestableListDragLayer,
};
export default withDNDContext(NestableListBase);
//# sourceMappingURL=NestableListBase.js.map