function walkDescendants({
  callback,
  getNodeKey,
  ignoreCollapsed,
  isPseudoRoot = false,
  node,
  parentNode = null,
  currentIndex,
  path = [],
  lowerSiblingCounts = [],
}) {
  // The pseudo-root is not considered in the path
  const selfPath = isPseudoRoot
    ? []
    : [...path, getNodeKey({ node, treeIndex: currentIndex })];
  const selfInfo = isPseudoRoot
    ? null
    : {
        node,
        parentNode,
        path: selfPath,
        lowerSiblingCounts,
        treeIndex: currentIndex,
      };

  if (!isPseudoRoot) {
    const 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
  let childIndex = currentIndex;
  const childCount = node.children.length;
  if (typeof node.children !== 'function') {
    for (let i = 0; i < childCount; i += 1) {
      childIndex = walkDescendants({
        callback,
        getNodeKey,
        ignoreCollapsed,
        node: node.children[i],
        parentNode: isPseudoRoot ? null : node,
        currentIndex: childIndex + 1,
        lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1],
        path: selfPath,
      });

      // Cut walk short if the callback returned false
      if (childIndex === false) {
        return false;
      }
    }
  }

  return childIndex;
}
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