react-sortable-tree-accessible
Version:
1,507 lines (1,301 loc) • 122 kB
JavaScript
'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