UNPKG

react-sortable-tree-node

Version:
1,308 lines (1,115 loc) 45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDescendantCount = getDescendantCount; exports.getVisibleNodeCount = getVisibleNodeCount; exports.getVisibleNodeInfoAtIndex = getVisibleNodeInfoAtIndex; exports.walk = walk; exports.map = map; exports.toggleExpandedForAll = toggleExpandedForAll; exports.changeNodeAtPath = changeNodeAtPath; exports.removeNodeAtPath = removeNodeAtPath; exports.removeNode = removeNode; exports.getNodeAtPath = getNodeAtPath; exports.addNodeUnderParent = addNodeUnderParent; exports.insertNode = insertNode; exports.getFlatDataFromTree = getFlatDataFromTree; exports.getTreeFromFlatData = getTreeFromFlatData; exports.isDescendant = isDescendant; exports.getDepth = getDepth; exports.find = find; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * Performs a depth-first traversal over all of the node descendants, * incrementing currentIndex by 1 for each */ function getNodeDataAtTreeIndexOrNextIndex(_ref) { var targetIndex = _ref.targetIndex, node = _ref.node, currentIndex = _ref.currentIndex, getNodeKey = _ref.getNodeKey, _ref$path = _ref.path, path = _ref$path === void 0 ? [] : _ref$path, _ref$lowerSiblingCoun = _ref.lowerSiblingCounts, lowerSiblingCounts = _ref$lowerSiblingCoun === void 0 ? [] : _ref$lowerSiblingCoun, _ref$ignoreCollapsed = _ref.ignoreCollapsed, ignoreCollapsed = _ref$ignoreCollapsed === void 0 ? true : _ref$ignoreCollapsed, _ref$isPseudoRoot = _ref.isPseudoRoot, isPseudoRoot = _ref$isPseudoRoot === void 0 ? false : _ref$isPseudoRoot; // The pseudo-root is not considered in the path var selfPath = !isPseudoRoot ? [].concat(_toConsumableArray(path), [getNodeKey({ node: node, treeIndex: currentIndex })]) : []; // Return target node when found if (currentIndex === targetIndex) { return { node: node, lowerSiblingCounts: lowerSiblingCounts, path: selfPath }; } // Add one and continue for nodes with no children or hidden children if (!node.children || ignoreCollapsed && node.expanded !== true) { return { nextIndex: currentIndex + 1 }; } // Iterate over each child and their descendants and return the // target node if childIndex reaches the targetIndex var childIndex = currentIndex + 1; var childCount = node.children.length; for (var i = 0; i < childCount; i += 1) { var result = getNodeDataAtTreeIndexOrNextIndex({ ignoreCollapsed: ignoreCollapsed, getNodeKey: getNodeKey, targetIndex: targetIndex, node: node.children[i], currentIndex: childIndex, lowerSiblingCounts: [].concat(_toConsumableArray(lowerSiblingCounts), [childCount - i - 1]), path: selfPath }); if (result.node) { return result; } childIndex = result.nextIndex; } // If the target node is not found, return the farthest traversed index return { nextIndex: childIndex }; } function getDescendantCount(_ref2) { var node = _ref2.node, _ref2$ignoreCollapsed = _ref2.ignoreCollapsed, ignoreCollapsed = _ref2$ignoreCollapsed === void 0 ? true : _ref2$ignoreCollapsed; return getNodeDataAtTreeIndexOrNextIndex({ getNodeKey: function getNodeKey() {}, ignoreCollapsed: ignoreCollapsed, node: node, currentIndex: 0, targetIndex: -1 }).nextIndex - 1; } /** * Walk all descendants of the given node, depth-first * * @param {Object} args - Function parameters * @param {function} args.callback - Function to call on each node * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves * as the parent of all the nodes in the tree * @param {Object} args.node - A tree node * @param {Object=} args.parentNode - The parent node of `node` * @param {number} args.currentIndex - The treeIndex of `node` * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the * previous nodes in this path * * @return {number|false} nextIndex - Index of the next sibling of `node`, * or false if the walk should be terminated */ function walkDescendants(_ref3) { var callback = _ref3.callback, getNodeKey = _ref3.getNodeKey, ignoreCollapsed = _ref3.ignoreCollapsed, _ref3$isPseudoRoot = _ref3.isPseudoRoot, isPseudoRoot = _ref3$isPseudoRoot === void 0 ? false : _ref3$isPseudoRoot, node = _ref3.node, _ref3$parentNode = _ref3.parentNode, parentNode = _ref3$parentNode === void 0 ? null : _ref3$parentNode, currentIndex = _ref3.currentIndex, _ref3$path = _ref3.path, path = _ref3$path === void 0 ? [] : _ref3$path, _ref3$lowerSiblingCou = _ref3.lowerSiblingCounts, lowerSiblingCounts = _ref3$lowerSiblingCou === void 0 ? [] : _ref3$lowerSiblingCou; // The pseudo-root is not considered in the path var selfPath = isPseudoRoot ? [] : [].concat(_toConsumableArray(path), [getNodeKey({ node: node, treeIndex: currentIndex })]); var selfInfo = isPseudoRoot ? null : { node: node, parentNode: parentNode, path: selfPath, lowerSiblingCounts: lowerSiblingCounts, treeIndex: currentIndex }; if (!isPseudoRoot) { var callbackResult = callback(selfInfo); // Cut walk short if the callback returned false if (callbackResult === false) { return false; } } // Return self on nodes with no children or hidden children if (!node.children || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) { return currentIndex; } // Get all descendants var childIndex = currentIndex; var childCount = node.children.length; if (typeof node.children !== 'function') { for (var i = 0; i < childCount; i += 1) { childIndex = walkDescendants({ callback: callback, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, node: node.children[i], parentNode: isPseudoRoot ? null : node, currentIndex: childIndex + 1, lowerSiblingCounts: [].concat(_toConsumableArray(lowerSiblingCounts), [childCount - i - 1]), path: selfPath }); // Cut walk short if the callback returned false if (childIndex === false) { return false; } } } return childIndex; } /** * Perform a change on the given node and all its descendants, traversing the tree depth-first * * @param {Object} args - Function parameters * @param {function} args.callback - Function to call on each node * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves * as the parent of all the nodes in the tree * @param {Object} args.node - A tree node * @param {Object=} args.parentNode - The parent node of `node` * @param {number} args.currentIndex - The treeIndex of `node` * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the * previous nodes in this path * * @return {number|false} nextIndex - Index of the next sibling of `node`, * or false if the walk should be terminated */ function mapDescendants(_ref4) { var callback = _ref4.callback, getNodeKey = _ref4.getNodeKey, ignoreCollapsed = _ref4.ignoreCollapsed, _ref4$isPseudoRoot = _ref4.isPseudoRoot, isPseudoRoot = _ref4$isPseudoRoot === void 0 ? false : _ref4$isPseudoRoot, node = _ref4.node, _ref4$parentNode = _ref4.parentNode, parentNode = _ref4$parentNode === void 0 ? null : _ref4$parentNode, currentIndex = _ref4.currentIndex, _ref4$path = _ref4.path, path = _ref4$path === void 0 ? [] : _ref4$path, _ref4$lowerSiblingCou = _ref4.lowerSiblingCounts, lowerSiblingCounts = _ref4$lowerSiblingCou === void 0 ? [] : _ref4$lowerSiblingCou; var nextNode = _objectSpread({}, node); // The pseudo-root is not considered in the path var selfPath = isPseudoRoot ? [] : [].concat(_toConsumableArray(path), [getNodeKey({ node: nextNode, treeIndex: currentIndex })]); var selfInfo = { node: nextNode, parentNode: parentNode, path: selfPath, lowerSiblingCounts: lowerSiblingCounts, treeIndex: currentIndex }; // Return self on nodes with no children or hidden children if (!nextNode.children || nextNode.expanded !== true && ignoreCollapsed && !isPseudoRoot) { return { treeIndex: currentIndex, node: callback(selfInfo) }; } // Get all descendants var childIndex = currentIndex; var childCount = nextNode.children.length; if (typeof nextNode.children !== 'function') { nextNode.children = nextNode.children.map(function (child, i) { var mapResult = mapDescendants({ callback: callback, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, node: child, parentNode: isPseudoRoot ? null : nextNode, currentIndex: childIndex + 1, lowerSiblingCounts: [].concat(_toConsumableArray(lowerSiblingCounts), [childCount - i - 1]), path: selfPath }); childIndex = mapResult.treeIndex; return mapResult.node; }); } return { node: callback(selfInfo), treeIndex: childIndex }; } /** * Count all the visible (expanded) descendants in the tree data. * * @param {!Object[]} treeData - Tree data * * @return {number} count */ function getVisibleNodeCount(_ref5) { var treeData = _ref5.treeData; var traverse = function traverse(node) { if (!node.children || node.expanded !== true || typeof node.children === 'function') { return 1; } return 1 + node.children.reduce(function (total, currentNode) { return total + traverse(currentNode); }, 0); }; return treeData.reduce(function (total, currentNode) { return total + traverse(currentNode); }, 0); } /** * Get the <targetIndex>th visible node in the tree data. * * @param {!Object[]} treeData - Tree data * @param {!number} targetIndex - The index of the node to search for * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * * @return {{ * node: Object, * path: []string|[]number, * lowerSiblingCounts: []number * }|null} node - The node at targetIndex, or null if not found */ function getVisibleNodeInfoAtIndex(_ref6) { var treeData = _ref6.treeData, targetIndex = _ref6.index, getNodeKey = _ref6.getNodeKey; if (!treeData || treeData.length < 1) { return null; } // Call the tree traversal with a pseudo-root node var result = getNodeDataAtTreeIndexOrNextIndex({ targetIndex: targetIndex, getNodeKey: getNodeKey, node: { children: treeData, expanded: true }, currentIndex: -1, path: [], lowerSiblingCounts: [], isPseudoRoot: true }); if (result.node) { return result; } return null; } /** * Walk descendants depth-first and call a callback on each * * @param {!Object[]} treeData - Tree data * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {function} callback - Function to call on each node * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return void */ function walk(_ref7) { var treeData = _ref7.treeData, getNodeKey = _ref7.getNodeKey, callback = _ref7.callback, _ref7$ignoreCollapsed = _ref7.ignoreCollapsed, ignoreCollapsed = _ref7$ignoreCollapsed === void 0 ? true : _ref7$ignoreCollapsed; if (!treeData || treeData.length < 1) { return; } walkDescendants({ callback: callback, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, isPseudoRoot: true, node: { children: treeData }, currentIndex: -1, path: [], lowerSiblingCounts: [] }); } /** * Perform a depth-first transversal of the descendants and * make a change to every node in the tree * * @param {!Object[]} treeData - Tree data * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {function} callback - Function to call on each node * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {Object[]} changedTreeData - The changed tree data */ function map(_ref8) { var treeData = _ref8.treeData, getNodeKey = _ref8.getNodeKey, callback = _ref8.callback, _ref8$ignoreCollapsed = _ref8.ignoreCollapsed, ignoreCollapsed = _ref8$ignoreCollapsed === void 0 ? true : _ref8$ignoreCollapsed; if (!treeData || treeData.length < 1) { return []; } return mapDescendants({ callback: callback, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, isPseudoRoot: true, node: { children: treeData }, currentIndex: -1, path: [], lowerSiblingCounts: [] }).node.children; } /** * Expand or close every node in the tree * * @param {!Object[]} treeData - Tree data * @param {?boolean} expanded - Whether the node is expanded or not * * @return {Object[]} changedTreeData - The changed tree data */ function toggleExpandedForAll(_ref9) { var treeData = _ref9.treeData, _ref9$expanded = _ref9.expanded, expanded = _ref9$expanded === void 0 ? true : _ref9$expanded; return map({ treeData: treeData, callback: function callback(_ref10) { var node = _ref10.node; return _objectSpread(_objectSpread({}, node), {}, { expanded: expanded }); }, getNodeKey: function getNodeKey(_ref11) { var treeIndex = _ref11.treeIndex; return treeIndex; }, ignoreCollapsed: false }); } /** * Replaces node at path with object, or callback-defined object * * @param {!Object[]} treeData * @param {number[]|string[]} path - Array of keys leading up to node to be changed * @param {function|any} newNode - Node to replace the node at the path with, or a function producing the new node * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {Object[]} changedTreeData - The changed tree data */ function changeNodeAtPath(_ref12) { var treeData = _ref12.treeData, path = _ref12.path, newNode = _ref12.newNode, getNodeKey = _ref12.getNodeKey, _ref12$ignoreCollapse = _ref12.ignoreCollapsed, ignoreCollapsed = _ref12$ignoreCollapse === void 0 ? true : _ref12$ignoreCollapse; var RESULT_MISS = 'RESULT_MISS'; var traverse = function traverse(_ref13) { var _ref13$isPseudoRoot = _ref13.isPseudoRoot, isPseudoRoot = _ref13$isPseudoRoot === void 0 ? false : _ref13$isPseudoRoot, node = _ref13.node, currentTreeIndex = _ref13.currentTreeIndex, pathIndex = _ref13.pathIndex; if (!isPseudoRoot && getNodeKey({ node: node, treeIndex: currentTreeIndex }) !== path[pathIndex]) { return RESULT_MISS; } if (pathIndex >= path.length - 1) { // If this is the final location in the path, return its changed form return typeof newNode === 'function' ? newNode({ node: node, treeIndex: currentTreeIndex }) : newNode; } else if (!node.children) { // If this node is part of the path, but has no children, return the unchanged node throw new Error('Path referenced children of node with no children.'); } var nextTreeIndex = currentTreeIndex + 1; for (var i = 0; i < node.children.length; i += 1) { var _result = traverse({ node: node.children[i], currentTreeIndex: nextTreeIndex, pathIndex: pathIndex + 1 }); // If the result went down the correct path if (_result !== RESULT_MISS) { if (_result) { // If the result was truthy (in this case, an object), // pass it to the next level of recursion up return _objectSpread(_objectSpread({}, node), {}, { children: [].concat(_toConsumableArray(node.children.slice(0, i)), [_result], _toConsumableArray(node.children.slice(i + 1))) }); } // If the result was falsy (returned from the newNode function), then // delete the node from the array. return _objectSpread(_objectSpread({}, node), {}, { children: [].concat(_toConsumableArray(node.children.slice(0, i)), _toConsumableArray(node.children.slice(i + 1))) }); } nextTreeIndex += 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed: ignoreCollapsed }); } return RESULT_MISS; }; // Use a pseudo-root node in the beginning traversal var 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; } /** * Removes the node at the specified path and returns the resulting treeData. * * @param {!Object[]} treeData * @param {number[]|string[]} path - Array of keys leading up to node to be deleted * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {Object[]} changedTreeData - The tree data with the node removed */ function removeNodeAtPath(_ref14) { var treeData = _ref14.treeData, path = _ref14.path, getNodeKey = _ref14.getNodeKey, _ref14$ignoreCollapse = _ref14.ignoreCollapsed, ignoreCollapsed = _ref14$ignoreCollapse === void 0 ? true : _ref14$ignoreCollapse; return changeNodeAtPath({ treeData: treeData, path: path, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, newNode: null // Delete the node }); } /** * Removes the node at the specified path and returns the resulting treeData. * * @param {!Object[]} treeData * @param {number[]|string[]} path - Array of keys leading up to node to be deleted * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {Object} result * @return {Object[]} result.treeData - The tree data with the node removed * @return {Object} result.node - The node that was removed * @return {number} result.treeIndex - The previous treeIndex of the removed node */ function removeNode(_ref15) { var treeData = _ref15.treeData, path = _ref15.path, getNodeKey = _ref15.getNodeKey, _ref15$ignoreCollapse = _ref15.ignoreCollapsed, ignoreCollapsed = _ref15$ignoreCollapse === void 0 ? true : _ref15$ignoreCollapse; var removedNode = null; var removedTreeIndex = null; var nextTreeData = changeNodeAtPath({ treeData: treeData, path: path, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, newNode: function newNode(_ref16) { var node = _ref16.node, treeIndex = _ref16.treeIndex; // Store the target node and delete it from the tree removedNode = node; removedTreeIndex = treeIndex; return null; } }); return { treeData: nextTreeData, node: removedNode, treeIndex: removedTreeIndex }; } /** * Gets the node at the specified path * * @param {!Object[]} treeData * @param {number[]|string[]} path - Array of keys leading up to node to be deleted * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {Object|null} nodeInfo - The node info at the given path, or null if not found */ function getNodeAtPath(_ref17) { var treeData = _ref17.treeData, path = _ref17.path, getNodeKey = _ref17.getNodeKey, _ref17$ignoreCollapse = _ref17.ignoreCollapsed, ignoreCollapsed = _ref17$ignoreCollapse === void 0 ? true : _ref17$ignoreCollapse; var foundNodeInfo = null; try { changeNodeAtPath({ treeData: treeData, path: path, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, newNode: function newNode(_ref18) { var node = _ref18.node, treeIndex = _ref18.treeIndex; foundNodeInfo = { node: node, treeIndex: treeIndex }; return node; } }); } catch (err) {// Ignore the error -- the null return will be explanation enough } return foundNodeInfo; } /** * Adds the node to the specified parent and returns the resulting treeData. * * @param {!Object[]} treeData * @param {!Object} newNode - The node to insert * @param {number|string} parentKey - The key of the to-be parentNode of the node * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * @param {boolean=} expandParent - If true, expands the parentNode specified by parentPath * @param {boolean=} addAsFirstChild - If true, adds new node as first child of tree * * @return {Object} result * @return {Object[]} result.treeData - The updated tree data * @return {number} result.treeIndex - The tree index at which the node was inserted */ function addNodeUnderParent(_ref19) { var treeData = _ref19.treeData, newNode = _ref19.newNode, _ref19$parentKey = _ref19.parentKey, parentKey = _ref19$parentKey === void 0 ? null : _ref19$parentKey, getNodeKey = _ref19.getNodeKey, _ref19$ignoreCollapse = _ref19.ignoreCollapsed, ignoreCollapsed = _ref19$ignoreCollapse === void 0 ? true : _ref19$ignoreCollapse, _ref19$expandParent = _ref19.expandParent, expandParent = _ref19$expandParent === void 0 ? false : _ref19$expandParent, _ref19$addAsFirstChil = _ref19.addAsFirstChild, addAsFirstChild = _ref19$addAsFirstChil === void 0 ? false : _ref19$addAsFirstChil; if (parentKey === null) { return { treeData: [].concat(_toConsumableArray(treeData || []), [newNode]), treeIndex: (treeData || []).length }; } var insertedTreeIndex = null; var hasBeenAdded = false; var changedTreeData = map({ treeData: treeData, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, callback: function callback(_ref20) { var node = _ref20.node, treeIndex = _ref20.treeIndex, path = _ref20.path; var key = path ? path[path.length - 1] : null; // Return nodes that are not the parent as-is if (hasBeenAdded || key !== parentKey) { return node; } hasBeenAdded = true; var parentNode = _objectSpread({}, node); if (expandParent) { parentNode.expanded = true; } // If no children exist yet, just add the single newNode if (!parentNode.children) { insertedTreeIndex = treeIndex + 1; return _objectSpread(_objectSpread({}, parentNode), {}, { children: [newNode] }); } if (typeof parentNode.children === 'function') { throw new Error('Cannot add to children defined by a function'); } var nextTreeIndex = treeIndex + 1; for (var i = 0; i < parentNode.children.length; i += 1) { nextTreeIndex += 1 + getDescendantCount({ node: parentNode.children[i], ignoreCollapsed: ignoreCollapsed }); } insertedTreeIndex = nextTreeIndex; var children = addAsFirstChild ? [newNode].concat(_toConsumableArray(parentNode.children)) : [].concat(_toConsumableArray(parentNode.children), [newNode]); return _objectSpread(_objectSpread({}, parentNode), {}, { children: children }); } }); if (!hasBeenAdded) { throw new Error('No node found with the given key.'); } return { treeData: changedTreeData, treeIndex: insertedTreeIndex }; } function addNodeAtDepthAndIndex(_ref21) { var targetDepth = _ref21.targetDepth, minimumTreeIndex = _ref21.minimumTreeIndex, newNode = _ref21.newNode, ignoreCollapsed = _ref21.ignoreCollapsed, expandParent = _ref21.expandParent, _ref21$isPseudoRoot = _ref21.isPseudoRoot, isPseudoRoot = _ref21$isPseudoRoot === void 0 ? false : _ref21$isPseudoRoot, isLastChild = _ref21.isLastChild, node = _ref21.node, currentIndex = _ref21.currentIndex, currentDepth = _ref21.currentDepth, getNodeKey = _ref21.getNodeKey, _ref21$path = _ref21.path, path = _ref21$path === void 0 ? [] : _ref21$path; var selfPath = function selfPath(n) { return isPseudoRoot ? [] : [].concat(_toConsumableArray(path), [getNodeKey({ node: n, treeIndex: currentIndex })]); }; // If the current position is the only possible place to add, add it if (currentIndex >= minimumTreeIndex - 1 || isLastChild && !(node.children && node.children.length)) { if (typeof node.children === 'function') { throw new Error('Cannot add to children defined by a function'); } else { var extraNodeProps = expandParent ? { expanded: true } : {}; var _nextNode = _objectSpread(_objectSpread(_objectSpread({}, node), extraNodeProps), {}, { children: node.children ? [newNode].concat(_toConsumableArray(node.children)) : [newNode] }); return { node: _nextNode, nextIndex: currentIndex + 2, insertedTreeIndex: currentIndex + 1, parentPath: selfPath(_nextNode), parentNode: isPseudoRoot ? null : _nextNode }; } } // If this is the target depth for the insertion, // i.e., where the newNode can be added to the current node's children if (currentDepth >= targetDepth - 1) { // Skip over nodes with no children or hidden children if (!node.children || typeof node.children === 'function' || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) { return { node: node, nextIndex: currentIndex + 1 }; } // Scan over the children to see if there's a place among them that fulfills // the minimumTreeIndex requirement var _childIndex = currentIndex + 1; var _insertedTreeIndex = null; var insertIndex = null; for (var i = 0; i < node.children.length; i += 1) { // If a valid location is found, mark it as the insertion location and // break out of the loop if (_childIndex >= minimumTreeIndex) { _insertedTreeIndex = _childIndex; insertIndex = i; break; } // Increment the index by the child itself plus the number of descendants it has _childIndex += 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed: ignoreCollapsed }); } // If no valid indices to add the node were found if (insertIndex === null) { // If the last position in this node's children is less than the minimum index // and there are more children on the level of this node, return without insertion if (_childIndex < minimumTreeIndex && !isLastChild) { return { node: node, nextIndex: _childIndex }; } // Use the last position in the children array to insert the newNode _insertedTreeIndex = _childIndex; insertIndex = node.children.length; } // Insert the newNode at the insertIndex var _nextNode2 = _objectSpread(_objectSpread({}, node), {}, { children: [].concat(_toConsumableArray(node.children.slice(0, insertIndex)), [newNode], _toConsumableArray(node.children.slice(insertIndex))) }); // Return node with successful insert result return { node: _nextNode2, nextIndex: _childIndex, insertedTreeIndex: _insertedTreeIndex, parentPath: selfPath(_nextNode2), parentNode: isPseudoRoot ? null : _nextNode2 }; } // Skip over nodes with no children or hidden children if (!node.children || typeof node.children === 'function' || node.expanded !== true && ignoreCollapsed && !isPseudoRoot) { return { node: node, nextIndex: currentIndex + 1 }; } // Get all descendants var insertedTreeIndex = null; var pathFragment = null; var parentNode = null; var childIndex = currentIndex + 1; var newChildren = node.children; if (typeof newChildren !== 'function') { newChildren = newChildren.map(function (child, i) { if (insertedTreeIndex !== null) { return child; } var mapResult = addNodeAtDepthAndIndex({ targetDepth: targetDepth, minimumTreeIndex: minimumTreeIndex, newNode: newNode, ignoreCollapsed: ignoreCollapsed, expandParent: expandParent, isLastChild: isLastChild && i === newChildren.length - 1, node: child, currentIndex: childIndex, currentDepth: currentDepth + 1, getNodeKey: getNodeKey, path: [] // Cannot determine the parent path until the children have been processed }); if ('insertedTreeIndex' in mapResult) { insertedTreeIndex = mapResult.insertedTreeIndex; parentNode = mapResult.parentNode; pathFragment = mapResult.parentPath; } childIndex = mapResult.nextIndex; return mapResult.node; }); } var nextNode = _objectSpread(_objectSpread({}, node), {}, { children: newChildren }); var result = { node: nextNode, nextIndex: childIndex }; if (insertedTreeIndex !== null) { result.insertedTreeIndex = insertedTreeIndex; result.parentPath = [].concat(_toConsumableArray(selfPath(nextNode)), _toConsumableArray(pathFragment)); result.parentNode = parentNode; } return result; } /** * Insert a node into the tree at the given depth, after the minimum index * * @param {!Object[]} treeData - Tree data * @param {!number} depth - The depth to insert the node at (the first level of the array being depth 0) * @param {!number} minimumTreeIndex - The lowest possible treeIndex to insert the node at * @param {!Object} newNode - The node to insert into the tree * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * @param {boolean=} expandParent - If true, expands the parent of the inserted node * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * * @return {Object} result * @return {Object[]} result.treeData - The tree data with the node added * @return {number} result.treeIndex - The tree index at which the node was inserted * @return {number[]|string[]} result.path - Array of keys leading to the node location after insertion * @return {Object} result.parentNode - The parent node of the inserted node */ function insertNode(_ref22) { var treeData = _ref22.treeData, targetDepth = _ref22.depth, minimumTreeIndex = _ref22.minimumTreeIndex, newNode = _ref22.newNode, _ref22$getNodeKey = _ref22.getNodeKey, getNodeKey = _ref22$getNodeKey === void 0 ? function () {} : _ref22$getNodeKey, _ref22$ignoreCollapse = _ref22.ignoreCollapsed, ignoreCollapsed = _ref22$ignoreCollapse === void 0 ? true : _ref22$ignoreCollapse, _ref22$expandParent = _ref22.expandParent, expandParent = _ref22$expandParent === void 0 ? false : _ref22$expandParent; if (!treeData && targetDepth === 0) { return { treeData: [newNode], treeIndex: 0, path: [getNodeKey({ node: newNode, treeIndex: 0 })], parentNode: null }; } var insertResult = addNodeAtDepthAndIndex({ targetDepth: targetDepth, minimumTreeIndex: minimumTreeIndex, newNode: newNode, ignoreCollapsed: ignoreCollapsed, expandParent: expandParent, getNodeKey: 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.'); } var treeIndex = insertResult.insertedTreeIndex; return { treeData: insertResult.node.children, treeIndex: treeIndex, path: [].concat(_toConsumableArray(insertResult.parentPath), [getNodeKey({ node: newNode, treeIndex: treeIndex })]), parentNode: insertResult.parentNode }; } /** * Get tree data flattened. * * @param {!Object[]} treeData - Tree data * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` * * @return {{ * node: Object, * path: []string|[]number, * lowerSiblingCounts: []number * }}[] nodes - The node array */ function getFlatDataFromTree(_ref23) { var treeData = _ref23.treeData, getNodeKey = _ref23.getNodeKey, _ref23$ignoreCollapse = _ref23.ignoreCollapsed, ignoreCollapsed = _ref23$ignoreCollapse === void 0 ? true : _ref23$ignoreCollapse; if (!treeData || treeData.length < 1) { return []; } var flattened = []; walk({ treeData: treeData, getNodeKey: getNodeKey, ignoreCollapsed: ignoreCollapsed, callback: function callback(nodeInfo) { flattened.push(nodeInfo); } }); return flattened; } /** * Generate a tree structure from flat data. * * @param {!Object[]} flatData * @param {!function=} getKey - Function to get the key from the nodeData * @param {!function=} getParentKey - Function to get the parent key from the nodeData * @param {string|number=} rootKey - The value returned by `getParentKey` that corresponds to the root node. * For example, if your nodes have id 1-99, you might use rootKey = 0 * * @return {Object[]} treeData - The flat data represented as a tree */ function getTreeFromFlatData(_ref24) { var flatData = _ref24.flatData, _ref24$getKey = _ref24.getKey, getKey = _ref24$getKey === void 0 ? function (node) { return node.id; } : _ref24$getKey, _ref24$getParentKey = _ref24.getParentKey, getParentKey = _ref24$getParentKey === void 0 ? function (node) { return node.parentId; } : _ref24$getParentKey, _ref24$rootKey = _ref24.rootKey, rootKey = _ref24$rootKey === void 0 ? '0' : _ref24$rootKey; if (!flatData) { return []; } var childrenToParents = {}; flatData.forEach(function (child) { var parentKey = getParentKey(child); if (parentKey in childrenToParents) { childrenToParents[parentKey].push(child); } else { childrenToParents[parentKey] = [child]; } }); if (!(rootKey in childrenToParents)) { return []; } var trav = function trav(parent) { var parentKey = getKey(parent); if (parentKey in childrenToParents) { return _objectSpread(_objectSpread({}, parent), {}, { children: childrenToParents[parentKey].map(function (child) { return trav(child); }) }); } return _objectSpread({}, parent); }; return childrenToParents[rootKey].map(function (child) { return trav(child); }); } /** * Check if a node is a descendant of another node. * * @param {!Object} older - Potential ancestor of younger node * @param {!Object} younger - Potential descendant of older node * * @return {boolean} */ function isDescendant(older, younger) { return !!older.children && typeof older.children !== 'function' && older.children.some(function (child) { return child === younger || isDescendant(child, younger); }); } /** * Get the maximum depth of the children (the depth of the root node is 0). * * @param {!Object} node - Node in the tree * @param {?number} depth - The current depth * * @return {number} maxDepth - The deepest depth in the tree */ function getDepth(node) { var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (!node.children) { return depth; } if (typeof node.children === 'function') { return depth + 1; } return node.children.reduce(function (deepest, child) { return Math.max(deepest, getDepth(child, depth + 1)); }, depth); } /** * Find nodes matching a search query in the tree, * * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index * @param {!Object[]} treeData - Tree data * @param {?string|number} searchQuery - Function returning a boolean to indicate whether the node is a match or not * @param {!function} searchMethod - Function returning a boolean to indicate whether the node is a match or not * @param {?number} searchFocusOffset - The offset of the match to focus on * (e.g., 0 focuses on the first match, 1 on the second) * @param {boolean=} expandAllMatchPaths - If true, expands the paths to any matched node * @param {boolean=} expandFocusMatchPaths - If true, expands the path to the focused node * * @return {Object[]} matches - An array of objects containing the matching `node`s, their `path`s and `treeIndex`s * @return {Object[]} treeData - The original tree data with all relevant nodes expanded. * If expandAllMatchPaths and expandFocusMatchPaths are both false, * it will be the same as the original tree data. */ function find(_ref25) { var getNodeKey = _ref25.getNodeKey, treeData = _ref25.treeData, searchQuery = _ref25.searchQuery, searchMethod = _ref25.searchMethod, searchFocusOffset = _ref25.searchFocusOffset, _ref25$expandAllMatch = _ref25.expandAllMatchPaths, expandAllMatchPaths = _ref25$expandAllMatch === void 0 ? false : _ref25$expandAllMatch, _ref25$expandFocusMat = _ref25.expandFocusMatchPaths, expandFocusMatchPaths = _ref25$expandFocusMat === void 0 ? true : _ref25$expandFocusMat; var matchCount = 0; var trav = function trav(_ref26) { var _ref26$isPseudoRoot = _ref26.isPseudoRoot, isPseudoRoot = _ref26$isPseudoRoot === void 0 ? false : _ref26$isPseudoRoot, node = _ref26.node, currentIndex = _ref26.currentIndex, _ref26$path = _ref26.path, path = _ref26$path === void 0 ? [] : _ref26$path; var matches = []; var isSelfMatch = false; var hasFocusMatch = false; // The pseudo-root is not considered in the path var selfPath = isPseudoRoot ? [] : [].concat(_toConsumableArray(path), [getNodeKey({ node: node, treeIndex: currentIndex })]); var extraInfo = isPseudoRoot ? null : { path: selfPath, treeIndex: currentIndex }; // Nodes with with children that aren't lazy var hasChildren = node.children && typeof node.children !== 'function' && node.children.length > 0; // Examine the current node to see if it is a match if (!isPseudoRoot && searchMethod(_objectSpread(_objectSpread({}, extraInfo), {}, { node: node, searchQuery: searchQuery }))) { if (matchCount === searchFocusOffset) { hasFocusMatch = true; } // Keep track of the number of matching nodes, so we know when the searchFocusOffset // is reached matchCount += 1; // We cannot add this node to the matches right away, as it may be changed // during the search of the descendants. The entire node is used in // comparisons between nodes inside the `matches` and `treeData` results // of this method (`find`) isSelfMatch = true; } var childIndex = currentIndex; var newNode = _objectSpread({}, node); if (hasChildren) { // Get all descendants newNode.children = newNode.children.map(function (child) { var mapResult = trav({ node: child, currentIndex: childIndex + 1, path: selfPath }); // Ignore hidden nodes by only advancing the index counter to the returned treeIndex // if the child is expanded. // // The child could have been expanded from the start, // or expanded due to a matching node being found in its descendants if (mapResult.node.expanded) { childIndex = mapResult.treeIndex; } else { childIndex += 1; } if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) { matches = [].concat(_toConsumableArray(matches), _toConsumableArray(mapResult.matches)); if (mapResult.hasFocusMatch) { hasFocusMatch = true; } // Expand the current node if it has descendants matching the search // and the settings are set to do so. if (expandAllMatchPaths && mapResult.matches.length > 0 || (expandAllMatchPaths || expandFocusMatchPaths) && mapResult.hasFocusMatch) { newNode.expanded = true; } } return mapResult.node; }); } // Cannot assign a treeIndex to hidden nodes if (!isPseudoRoot && !newNode.expanded) { matches = matches.map(function (match) { return _objectSpread(_objectSpread({}, match), {}, { treeIndex: null }); }); } // Add this node to the matches if it fits the search criteria. // This is performed at the last minute so newNode can be sent in its final form. if (isSelfMatch) { matches = [_objectSpread(_objectSpread({}, extraInfo), {}, { node: newNode })].concat(_toConsumableArray(matches)); } return { node: matches.length > 0 ? newNode : node, matches: matches, hasFocusMatch: hasFocusMatch, treeIndex: childIndex }; }; var result = trav({ node: { children: treeData }, isPseudoRoot: true, currentIndex: -1 }); return { matches: result.matches, treeData: result.node.children }; }