UNPKG

react-sortable-tree-accessible

Version:
1,507 lines (1,301 loc) 122 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var withScrolling = require('frontend-collective-react-dnd-scrollzone'); var withScrolling__default = _interopDefault(withScrolling); var isEqual = _interopDefault(require('lodash.isequal')); var PropTypes = _interopDefault(require('prop-types')); var React = require('react'); var React__default = _interopDefault(React); var reactDnd = require('react-dnd'); var reactDndMultiBackend = require('react-dnd-multi-backend'); var KeyboardBackend = require('react-dnd-accessible-backend'); var KeyboardBackend__default = _interopDefault(KeyboardBackend); var MouseBackEnd = _interopDefault(require('react-dnd-mouse-backend')); var reactVirtualized = require('react-virtualized'); var reactDom = require('react-dom'); function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 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 _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 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 _objectSpread2(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 _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ function componentWillMount() { // Call this.constructor.gDSFP to support sub-classes. var state = this.constructor.getDerivedStateFromProps(this.props, this.state); if (state !== null && state !== undefined) { this.setState(state); } } function componentWillReceiveProps(nextProps) { // Call this.constructor.gDSFP to support sub-classes. // Use the setState() updater to ensure state isn't stale in certain edge cases. function updater(prevState) { var state = this.constructor.getDerivedStateFromProps(nextProps, prevState); return state !== null && state !== undefined ? state : null; } // Binding "this" is important for shallow renderer support. this.setState(updater.bind(this)); } function componentWillUpdate(nextProps, nextState) { try { var prevProps = this.props; var prevState = this.state; this.props = nextProps; this.state = nextState; this.__reactInternalSnapshotFlag = true; this.__reactInternalSnapshot = this.getSnapshotBeforeUpdate( prevProps, prevState ); } finally { this.props = prevProps; this.state = prevState; } } // React may warn about cWM/cWRP/cWU methods being deprecated. // Add a flag to suppress these warnings for this special case. componentWillMount.__suppressDeprecationWarning = true; componentWillReceiveProps.__suppressDeprecationWarning = true; componentWillUpdate.__suppressDeprecationWarning = true; function polyfill(Component) { var prototype = Component.prototype; if (!prototype || !prototype.isReactComponent) { throw new Error('Can only polyfill class components'); } if ( typeof Component.getDerivedStateFromProps !== 'function' && typeof prototype.getSnapshotBeforeUpdate !== 'function' ) { return Component; } // If new component APIs are defined, "unsafe" lifecycles won't be called. // Error if any of these lifecycles are present, // Because they would work differently between older and newer (16.3+) versions of React. var foundWillMountName = null; var foundWillReceivePropsName = null; var foundWillUpdateName = null; if (typeof prototype.componentWillMount === 'function') { foundWillMountName = 'componentWillMount'; } else if (typeof prototype.UNSAFE_componentWillMount === 'function') { foundWillMountName = 'UNSAFE_componentWillMount'; } if (typeof prototype.componentWillReceiveProps === 'function') { foundWillReceivePropsName = 'componentWillReceiveProps'; } else if (typeof prototype.UNSAFE_componentWillReceiveProps === 'function') { foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps'; } if (typeof prototype.componentWillUpdate === 'function') { foundWillUpdateName = 'componentWillUpdate'; } else if (typeof prototype.UNSAFE_componentWillUpdate === 'function') { foundWillUpdateName = 'UNSAFE_componentWillUpdate'; } if ( foundWillMountName !== null || foundWillReceivePropsName !== null || foundWillUpdateName !== null ) { var componentName = Component.displayName || Component.name; var newApiName = typeof Component.getDerivedStateFromProps === 'function' ? 'getDerivedStateFromProps()' : 'getSnapshotBeforeUpdate()'; throw Error( 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + componentName + ' uses ' + newApiName + ' but also contains the following legacy lifecycles:' + (foundWillMountName !== null ? '\n ' + foundWillMountName : '') + (foundWillReceivePropsName !== null ? '\n ' + foundWillReceivePropsName : '') + (foundWillUpdateName !== null ? '\n ' + foundWillUpdateName : '') + '\n\nThe above lifecycles should be removed. Learn more about this warning here:\n' + 'https://fb.me/react-async-component-lifecycle-hooks' ); } // React <= 16.2 does not support static getDerivedStateFromProps. // As a workaround, use cWM and cWRP to invoke the new static lifecycle. // Newer versions of React will ignore these lifecycles if gDSFP exists. if (typeof Component.getDerivedStateFromProps === 'function') { prototype.componentWillMount = componentWillMount; prototype.componentWillReceiveProps = componentWillReceiveProps; } // React <= 16.2 does not support getSnapshotBeforeUpdate. // As a workaround, use cWU to invoke the new lifecycle. // Newer versions of React will ignore that lifecycle if gSBU exists. if (typeof prototype.getSnapshotBeforeUpdate === 'function') { if (typeof prototype.componentDidUpdate !== 'function') { throw new Error( 'Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype' ); } prototype.componentWillUpdate = componentWillUpdate; var componentDidUpdate = prototype.componentDidUpdate; prototype.componentDidUpdate = function componentDidUpdatePolyfill( prevProps, prevState, maybeSnapshot ) { // 16.3+ will not execute our will-update method; // It will pass a snapshot value to did-update though. // Older versions will require our polyfilled will-update value. // We need to handle both cases, but can't just check for the presence of "maybeSnapshot", // Because for <= 15.x versions this might be a "prevContext" object. // We also can't just check "__reactInternalSnapshot", // Because get-snapshot might return a falsy value. // So check for the explicit __reactInternalSnapshotFlag flag to determine behavior. var snapshot = this.__reactInternalSnapshotFlag ? this.__reactInternalSnapshot : maybeSnapshot; componentDidUpdate.call(this, prevProps, prevState, snapshot); }; } return Component; } /** * 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 = _objectSpread2({}, 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 _objectSpread2(_objectSpread2({}, 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; } 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 _objectSpread2(_objectSpread2({}, 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 _objectSpread2(_objectSpread2({}, 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 addAsFirstChild ? { treeData: [newNode].concat(_toConsumableArray(treeData || [])), treeIndex: 0 } : { 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 = _objectSpread2({}, node); if (expandParent) { parentNode.expanded = true; } // If no children exist yet, just add the single newNode if (!parentNode.children) { insertedTreeIndex = treeIndex + 1; return _objectSpread2(_objectSpread2({}, 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 _objectSpread2(_objectSpread2({}, 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 = _objectSpread2(_objectSpread2(_objectSpread2({}, 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 = _objectSpread2(_objectSpread2({}, 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 = _objectSpread2(_objectSpread2({}, 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 _objectSpread2(_objectSpread2({}, parent), {}, { children: childrenToParents[parentKey].map(function (child) { return trav(child); }) }); } return _objectSpread2({}, 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 expa