react-sortable-tree-node
Version:
react-sortable-tree-node
1,308 lines (1,115 loc) • 45 kB
JavaScript
"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
};
}