UNPKG

mui-draggable-treeview

Version:

React Treeview component built on Material-UI with drag and drop features

398 lines (385 loc) 18.8 kB
import React, { useState, useEffect } from 'react'; import MuiTreeView from '@material-ui/lab/TreeView'; import { withTheme, makeStyles as makeStyles$1, createStyles as createStyles$1 } from '@material-ui/core/styles'; import { makeStyles, createStyles, Typography } from '@material-ui/core'; import { TreeItem } from '@material-ui/lab'; import { IndeterminateCheckBox, AddBox } from '@material-ui/icons'; import styled from 'styled-components'; import Immutable from 'immutable'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; } var StyledTreeItem = function (props) { var classes = useTreeItemStyles(); var labelText = props.labelText, LabelIcon = props.labelIcon, labelInfo = props.labelInfo, labelIconUrl = props.labelIconUrl, other = __rest(props, ["labelText", "labelIcon", "labelInfo", "labelIconUrl"]); var handleDragOver = function (ev, isBeforeDestinationNode) { ev.stopPropagation(); if (!props.validateDragOver(isBeforeDestinationNode)) return; ev.preventDefault(); ev.currentTarget.style.height = "30px"; ev.currentTarget.style.borderWidth = "3px"; }; var handleDragLeave = function (ev) { ev.stopPropagation(); ev.currentTarget.style.height = "3px"; ev.currentTarget.style.borderWidth = "0px"; }; var handleOrderChange = function (ev, isBeforeDestinationNode) { ev.stopPropagation(); ev.preventDefault(); if (props.onNodeReOrder) props.onNodeReOrder(ev, isBeforeDestinationNode); ev.currentTarget.style.height = "3px"; ev.currentTarget.style.borderWidth = "0px"; }; var iconContainerStyle = props.node.children && props.node.children.length > 0 ? classes.iconContainer : classes.hiddenIconContainer; var labelStyle = !(labelIconUrl || LabelIcon) ? classes.label : classes.labelWithIcon; var renderLabel = function () { var labelRootStyle = props.node.children && props.node.children.length > 0 ? classes.labelRootParent : classes.labelRootChild; labelRootStyle += " " + (props.node.disabled ? classes.disabled : ""); return (React.createElement(React.Fragment, null, props.depth !== 0 && (React.createElement("div", { onDrop: function (ev) { return handleOrderChange(ev, true); }, onDragOver: function (ev) { return handleDragOver(ev, true); }, onDragLeave: handleDragLeave, draggable: false, style: { height: "3px", border: "dashed 0px white", backgroundColor: "transparent !important", } })), React.createElement("div", { className: labelRootStyle, draggable: false }, (labelIconUrl || LabelIcon) && (React.createElement("div", { className: classes.labelIcon }, LabelIcon ? React.createElement(LabelIcon, { color: "inherit" }) : React.createElement("img", { src: labelIconUrl, alt: "image" }))), React.createElement(Typography, { variant: "body2", className: classes.labelText }, labelText), labelInfo && (React.createElement(Typography, { variant: "caption", color: "inherit" }, labelInfo))), !props.isExpanded && (React.createElement("div", { onDrop: function (ev) { return handleOrderChange(ev, false); }, onDragOver: function (ev) { return handleDragOver(ev, false); }, onDragLeave: handleDragLeave, draggable: false, style: { height: "3px", border: "dashed 0px white", backgroundColor: "transparent !important", } })))); }; return (React.createElement(TreeItem, __assign({ label: renderLabel(), key: props.nodeId + "_treeItem", classes: { root: classes.root, content: classes.content, label: labelStyle, iconContainer: iconContainerStyle, group: classes.group, expanded: classes.expanded, } }, other))); }; var useTreeItemStyles = makeStyles(function (theme) { return createStyles({ root: { color: theme.palette.text.secondary, "&:focus > $content": { color: "var(--tree-view-color)", }, "&:focus > $content $label, &:hover > $content $label, &$selected > $content $label": { backgroundColor: "transparent", }, marginBottom: "10px", }, disabled: { opacity: "0.5", }, group: { marginLeft: "23px", }, expanded: {}, content: { color: theme.palette.text.secondary, paddingRight: theme.spacing(1), fontWeight: theme.typography.fontWeightMedium, "$expanded > &": { fontWeight: theme.typography.fontWeightRegular, paddingBottom: "10px", }, alignItems: "baseline", marginBottom: "10px", }, label: { fontWeight: "inherit", color: "inherit", paddingLeft: "0px !important", bottom: "3px", }, labelWithIcon: { fontWeight: "inherit", color: "inherit", paddingLeft: "0px !important", top: "1px", }, labelRootParent: { display: "flex", alignItems: "center", padding: "0 0 4px 0", marginLeft: "3px", }, labelRootChild: { display: "flex", alignItems: "center", padding: "0 0 4px 0", }, labelIcon: { marginRight: "5px", }, labelText: { fontWeight: "inherit", flexGrow: 1, }, iconContainer: { width: "15px", marginLeft: "1px", }, hiddenIconContainer: { display: "none", }, }); }); var moveNode = function (node, sourceNode, destinationNodeId) { if (node.id === destinationNodeId) { if (!node.children) node.children = []; var order = node.children && node.children.length > 0 ? node.children[node.children.length - 1].order || 0 : 0; sourceNode.order = order + 1; node.children.push(sourceNode); return true; } if (node.children) { for (var index = 0; index < node.children.length; index++) { var isMoved = moveNode(node.children[index], sourceNode, destinationNodeId); if (isMoved) return true; } } return false; }; var reOrderNodes = function (node, draggedTreeNode, destinationNodeId, parentNodeIdOfDestinationNode, addBeforeDestinationNode) { var _a; if (node.id === parentNodeIdOfDestinationNode && node.children && node.children.length > 0) { if (((_a = draggedTreeNode.parentNode) === null || _a === void 0 ? void 0 : _a.id) !== parentNodeIdOfDestinationNode) { if (!node.children.some(function (x) { return x.id === draggedTreeNode.node.id; })) { node.children.push(draggedTreeNode.node); } } node.children.sort(function (a, b) { return (a.order || 0) - (b.order || 0); }); var draggedNode = node.children.find(function (x) { return x.id === draggedTreeNode.node.id; }); if (!draggedNode) return {}; for (var index = 0; index < node.children.length; index++) { if (node.children[index].id === destinationNodeId) { var result = { siblingsOfDraggedTreeNode: [] }; draggedNode.order = node.children[index].order || 0; if (!addBeforeDestinationNode) draggedNode.order++; result.draggedTreeNodeOrder = draggedNode.order; for (var subIndex = addBeforeDestinationNode ? index : index + 1; subIndex < node.children.length; subIndex++) { if (node.children[subIndex].id !== draggedNode.id) { node.children[subIndex].order = (node.children[subIndex].order || 0) + 1; result.siblingsOfDraggedTreeNode.push(node.children[subIndex]); } } return result; } } } if (node.children) { for (var index = 0; index < node.children.length; index++) { var result = reOrderNodes(node.children[index], draggedTreeNode, destinationNodeId, parentNodeIdOfDestinationNode, addBeforeDestinationNode); if (result) return result; } } return null; }; var deleteNode = function (node, sourceNodeId, parentNodeId) { var _a; if (node.id === parentNodeId) { node.children = (_a = node.children) === null || _a === void 0 ? void 0 : _a.filter(function (x) { return x.id !== sourceNodeId; }); return true; } if (node.children) { for (var index = 0; index < node.children.length; index++) { var isDeleted = deleteNode(node.children[index], sourceNodeId, parentNodeId); if (isDeleted) return true; } } return false; }; var MuiDraggableTreeView = function (props) { var _a, _b; var _c = useState(props.tree), tree = _c[0], setTree = _c[1]; var _d = useState([]), allParentIds = _d[0], setAllParentIds = _d[1]; var _f = useState([props.tree.id]), expanded = _f[0], setExpanded = _f[1]; var classes = useStyles(); var draggedTreeNode; useEffect(function () { var ids = []; populateAllParentNodeIds(ids, props.tree); setAllParentIds(ids); setTree(props.tree); }, [props.tree]); var handleDragOver = function (ev, destinationNode, depth) { var _a; ev.stopPropagation(); var allowToDrag = true; if (!draggedTreeNode) return; if (((_a = draggedTreeNode.parentNode) === null || _a === void 0 ? void 0 : _a.id) === destinationNode.id || draggedTreeNode.node.id === destinationNode.id || (draggedTreeNode.node.children && draggedTreeNode.node.children.length > 0 && draggedTreeNode.depth < depth)) return; if (props.onNodeDragOver) { allowToDrag = props.onNodeDragOver(draggedTreeNode.node, destinationNode); } if (allowToDrag) { ev.preventDefault(); ev.currentTarget.classList.add(classes.dragOver); } }; var handleDrop = function (ev, destinationNode) { ev.preventDefault(); ev.stopPropagation(); if (!draggedTreeNode || !draggedTreeNode.parentNode) return; var newTree = Immutable.fromJS(tree).toJS(); moveNode(newTree, draggedTreeNode.node, destinationNode.id); deleteNode(newTree, draggedTreeNode.node.id, draggedTreeNode.parentNode.id); setTree(newTree); ev.currentTarget.classList.remove(classes.dragOver); if (props.onNodeDrop) props.onNodeDrop(draggedTreeNode.node, destinationNode); }; var handleDragStart = function (ev, parentNode, node, depth) { ev.stopPropagation(); draggedTreeNode = { parentNode: parentNode, node: node, depth: depth }; }; var validateDragOver = function (node, depth, isBeforeDestinationNode) { if (!draggedTreeNode) return false; if (draggedTreeNode.node.id.toString() === node.id || (draggedTreeNode.parentNode && draggedTreeNode.parentNode.id.toString() === node.id) || (draggedTreeNode.node.children && draggedTreeNode.node.children.length > 0 && draggedTreeNode.depth < depth)) return false; if (props.onNodeReOrderOver && !props.onNodeReOrderOver(draggedTreeNode.node, node, isBeforeDestinationNode)) return false; return true; }; var handleDragLeave = function (ev) { ev.currentTarget.classList.remove(classes.dragOver); }; var handleNodeReOrder = function (ev, destinationNode, parentNodeIdOfDestinationNode, addBeforeDestinationNode) { ev.preventDefault(); ev.stopPropagation(); if (!draggedTreeNode) return; var newTree = Immutable.fromJS(tree).toJS(); var result = reOrderNodes(newTree, draggedTreeNode, destinationNode.id, parentNodeIdOfDestinationNode, addBeforeDestinationNode); if (!result) return; draggedTreeNode.node.order = result.draggedTreeNodeOrder; if (draggedTreeNode.parentNode && draggedTreeNode.parentNode.id !== parentNodeIdOfDestinationNode) { deleteNode(newTree, draggedTreeNode.node.id, draggedTreeNode.parentNode.id); } setTree(newTree); if (props.onNodeReOrder) props.onNodeReOrder(draggedTreeNode.node, destinationNode); }; var populateAllParentNodeIds = function (ids, node) { var _a; if (node.children) ids.push(node.id); (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(function (x) { populateAllParentNodeIds(ids, x); }); }; var renderTree = function (parentNode, node, depth) { return (React.createElement(StyledTreeItem, { key: node.id, isExpanded: expanded.some(function (x) { return x === node.id; }), nodeId: node.id, labelIcon: node.icon, labelIconUrl: node.iconUrl, node: node, depth: depth, draggable: parentNode !== null && props.enableDragAndDrop, onNodeReOrder: function (ev, isBeforeDestinationNode) { return parentNode && handleNodeReOrder(ev, node, parentNode.id, isBeforeDestinationNode); }, validateDragOver: function (isBeforeDestinationNode) { return validateDragOver(node, depth, isBeforeDestinationNode); }, onDrop: function (ev) { return handleDrop(ev, node); }, onDragLeave: handleDragLeave, onDragStart: function (ev) { return parentNode && handleDragStart(ev, parentNode, node, depth); }, onDragOver: function (ev) { return handleDragOver(ev, node, depth); }, onLabelClick: function () { var _a; return (_a = props.onNodeSelected) === null || _a === void 0 ? void 0 : _a.call(props, node); }, labelText: node.name }, node.children ? node.children .sort(function (a, b) { return (a.order || 0) - (b.order || 0); }) .map(function (childNode) { return renderTree(node, childNode, depth + 1); }) : null)); }; var handleToggle = function (_e, nodeIds) { setExpanded(nodeIds); }; return (React.createElement("div", { className: props.className }, React.createElement("div", { className: classes.expandCollapseButtonContainer }, React.createElement("div", { className: classes.expandCollapseAllButton, onClick: function () { return setExpanded(allParentIds); } }, "ExpandAll"), React.createElement("div", { className: classes.expandCollapseAllButton, onClick: function () { return setExpanded([props.tree.id]); } }, "CollapseAll")), React.createElement("div", null, React.createElement(MuiTreeView, __assign({}, props, { expanded: expanded, className: (_b = (_a = props.classes) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : classes.root, defaultCollapseIcon: React.createElement(CollapseIcon, null), defaultExpandIcon: React.createElement(ExpandIcon, null), onNodeToggle: handleToggle }), renderTree(null, tree, 0))))); }; var MuiDraggableTreeView$1 = withTheme(MuiDraggableTreeView); var CollapseIcon = styled(IndeterminateCheckBox)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n transform: scale(1.3);\n"], ["\n transform: scale(1.3);\n"]))); var ExpandIcon = styled(AddBox)(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n transform: scale(1.3);\n"], ["\n transform: scale(1.3);\n"]))); var useStyles = makeStyles$1(createStyles$1({ root: { flexGrow: 1, }, expandCollapseAllButton: { textDecoration: "underline", color: "rgb(0, 145, 255)", cursor: "pointer", marginLeft: "10px", }, expandCollapseButtonContainer: { display: "flex", justifyContent: "flex-end", }, dragOver: { backgroundColor: "#bdbdbd", borderBottomRightRadius: "16px", borderTopRightRadius: "16px", transition: "opacity 200ms", }, })); var templateObject_1, templateObject_2; export { MuiDraggableTreeView$1 as MuiDraggableTreeView };