@flasham/react-sortable-tree
Version:
Drag-and-drop sortable component for nested data and hierarchies
1,580 lines (1,487 loc) • 204 kB
JavaScript
import React, { forwardRef, useRef, useMemo, useReducer, useLayoutEffect, useEffect, useImperativeHandle, memo, Component, Children, cloneElement } from 'react';
import { DragSource, DropTarget, DndProvider, DndContext } from 'react-dnd';
import { flushSync } from 'react-dom';
const defaultGetNodeKey = ({ treeIndex }) => treeIndex;
// Cheap hack to get the text of a react object
const getReactElementText = (parent) => {
if (typeof parent === 'string') {
return parent;
}
if (parent === undefined ||
typeof parent !== 'object' ||
!parent.props ||
!parent.props.children ||
(typeof parent.props.children !== 'string' &&
typeof parent.props.children !== 'object')) {
return '';
}
if (typeof parent.props.children === 'string') {
return parent.props.children;
}
return parent.props.children
.map((child) => getReactElementText(child))
.join('');
};
// Search for a query string inside a node property
const stringSearch = (key, searchQuery, node, path, treeIndex) => {
if (typeof node[key] === 'function') {
// Search within text after calling its function to generate the text
return String(node[key]({ node, path, treeIndex })).includes(searchQuery);
}
if (typeof node[key] === 'object') {
// Search within text inside react elements
return getReactElementText(node[key]).includes(searchQuery);
}
// Search within string
return node[key] && String(node[key]).includes(searchQuery);
};
const defaultSearchMethod = ({ node, path, treeIndex, searchQuery, }) => {
return (stringSearch('title', searchQuery, node, path, treeIndex) ||
stringSearch('subtitle', searchQuery, node, path, treeIndex));
};
// @ts-nocheck
/**
* Performs a depth-first traversal over all of the node descendants,
* incrementing currentIndex by 1 for each
*/
const getNodeDataAtTreeIndexOrNextIndex = ({ targetIndex, node, currentIndex, getNodeKey, path = [], lowerSiblingCounts = [], ignoreCollapsed = true, isPseudoRoot = false, }) => {
// The pseudo-root is not considered in the path
const selfPath = isPseudoRoot
? []
: [...path, getNodeKey({ node, treeIndex: currentIndex })];
// Return target node when found
if (currentIndex === targetIndex) {
return {
node,
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
let childIndex = currentIndex + 1;
const childCount = node.children.length;
for (let i = 0; i < childCount; i += 1) {
const result = getNodeDataAtTreeIndexOrNextIndex({
ignoreCollapsed,
getNodeKey,
targetIndex,
node: node.children[i],
currentIndex: childIndex,
lowerSiblingCounts: [...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 };
};
const getDescendantCount = ({ node, ignoreCollapsed = true, }) => {
return (getNodeDataAtTreeIndexOrNextIndex({
getNodeKey: () => { },
ignoreCollapsed,
node,
currentIndex: 0,
targetIndex: -1,
}).nextIndex - 1);
};
const walkDescendants = ({ callback, getNodeKey, ignoreCollapsed, isPseudoRoot = false, node, parentNode = undefined, currentIndex, path = [], lowerSiblingCounts = [], }) => {
// The pseudo-root is not considered in the path
const selfPath = isPseudoRoot
? []
: [...path, getNodeKey({ node, treeIndex: currentIndex })];
const selfInfo = isPseudoRoot
? undefined
: {
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 ? undefined : 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;
};
const mapDescendants = ({ callback, getNodeKey, ignoreCollapsed, isPseudoRoot = false, node, parentNode = undefined, currentIndex, path = [], lowerSiblingCounts = [], }) => {
const nextNode = { ...node };
// The pseudo-root is not considered in the path
const selfPath = isPseudoRoot
? []
: [...path, getNodeKey({ node: nextNode, treeIndex: currentIndex })];
const selfInfo = {
node: nextNode,
parentNode,
path: selfPath,
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
let childIndex = currentIndex;
const childCount = nextNode.children.length;
if (typeof nextNode.children !== 'function') {
nextNode.children = nextNode.children.map((child, i) => {
const mapResult = mapDescendants({
callback,
getNodeKey,
ignoreCollapsed,
node: child,
parentNode: isPseudoRoot ? undefined : nextNode,
currentIndex: childIndex + 1,
lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1],
path: selfPath,
});
childIndex = mapResult.treeIndex;
return mapResult.node;
});
}
return {
node: callback(selfInfo),
treeIndex: childIndex,
};
};
const getVisibleNodeCount = ({ treeData }) => {
const traverse = (node) => {
if (!node.children ||
node.expanded !== true ||
typeof node.children === 'function') {
return 1;
}
return (1 +
node.children.reduce((total, currentNode) => total + traverse(currentNode), 0));
};
return treeData.reduce((total, currentNode) => total + traverse(currentNode), 0);
};
const getVisibleNodeInfoAtIndex = ({ treeData, index: targetIndex, getNodeKey, }) => {
if (!treeData || treeData.length === 0) {
return undefined;
}
// Call the tree traversal with a pseudo-root node
const result = getNodeDataAtTreeIndexOrNextIndex({
targetIndex,
getNodeKey,
node: {
children: treeData,
expanded: true,
},
currentIndex: -1,
path: [],
lowerSiblingCounts: [],
isPseudoRoot: true,
});
if (result.node) {
return result;
}
return undefined;
};
const walk = ({ treeData, getNodeKey, callback, ignoreCollapsed = true, }) => {
if (!treeData || treeData.length === 0) {
return;
}
walkDescendants({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot: true,
node: { children: treeData },
currentIndex: -1,
path: [],
lowerSiblingCounts: [],
});
};
const map = ({ treeData, getNodeKey, callback, ignoreCollapsed = true, }) => {
if (!treeData || treeData.length === 0) {
return [];
}
return mapDescendants({
callback,
getNodeKey,
ignoreCollapsed,
isPseudoRoot: true,
node: { children: treeData },
currentIndex: -1,
path: [],
lowerSiblingCounts: [],
}).node.children;
};
const toggleExpandedForAll = ({ treeData, expanded = true, }) => {
return map({
treeData,
callback: ({ node }) => ({ ...node, expanded }),
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: false,
});
};
const changeNodeAtPath = ({ treeData, path, newNode, getNodeKey, ignoreCollapsed = true, }) => {
const RESULT_MISS = 'RESULT_MISS';
const traverse = ({ isPseudoRoot = false, node, currentTreeIndex, pathIndex, }) => {
if (!isPseudoRoot &&
getNodeKey({ 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, 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.');
}
let nextTreeIndex = currentTreeIndex + 1;
for (let i = 0; i < node.children.length; i += 1) {
const 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 {
...node,
children: [
...node.children.slice(0, i),
result,
...node.children.slice(i + 1),
],
};
}
// If the result was falsy (returned from the newNode function), then
// delete the node from the array.
return {
...node,
children: [
...node.children.slice(0, i),
...node.children.slice(i + 1),
],
};
}
nextTreeIndex +=
1 + getDescendantCount({ node: node.children[i], ignoreCollapsed });
}
return RESULT_MISS;
};
// Use a pseudo-root node in the beginning traversal
const 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;
};
const removeNodeAtPath = ({ treeData, path, getNodeKey, ignoreCollapsed = true, }) => {
return changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: undefined, // Delete the node
});
};
const removeNode = ({ treeData, path, getNodeKey, ignoreCollapsed = true, }) => {
let removedNode;
let removedTreeIndex;
const nextTreeData = changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: ({ node, treeIndex }) => {
// Store the target node and delete it from the tree
removedNode = node;
removedTreeIndex = treeIndex;
return undefined;
},
});
return {
treeData: nextTreeData,
node: removedNode,
treeIndex: removedTreeIndex,
};
};
const getNodeAtPath = ({ treeData, path, getNodeKey, ignoreCollapsed = true, }) => {
let foundNodeInfo;
try {
changeNodeAtPath({
treeData,
path,
getNodeKey,
ignoreCollapsed,
newNode: ({ node, treeIndex }) => {
foundNodeInfo = { node, treeIndex };
return node;
},
});
}
catch {
// Ignore the error -- the null return will be explanation enough
}
return foundNodeInfo;
};
const addNodeUnderParent = ({ treeData, newNode, parentKey = undefined, getNodeKey, ignoreCollapsed = true, expandParent = false, addAsFirstChild = false, }) => {
if (parentKey === null || parentKey === undefined) {
return addAsFirstChild
? {
treeData: [newNode, ...(treeData || [])],
treeIndex: 0,
}
: {
treeData: [...(treeData || []), newNode],
treeIndex: (treeData || []).length,
};
}
let insertedTreeIndex;
let hasBeenAdded = false;
const changedTreeData = map({
treeData,
getNodeKey,
ignoreCollapsed,
callback: ({ node, treeIndex, path }) => {
const key = path ? path.at(-1) : undefined;
// Return nodes that are not the parent as-is
if (hasBeenAdded || key !== parentKey) {
return node;
}
hasBeenAdded = true;
const parentNode = {
...node,
};
if (expandParent) {
parentNode.expanded = true;
}
// If no children exist yet, just add the single newNode
if (!parentNode.children) {
insertedTreeIndex = treeIndex + 1;
return {
...parentNode,
children: [newNode],
};
}
if (typeof parentNode.children === 'function') {
throw new TypeError('Cannot add to children defined by a function');
}
let nextTreeIndex = treeIndex + 1;
for (let i = 0; i < parentNode.children.length; i += 1) {
nextTreeIndex +=
1 +
getDescendantCount({ node: parentNode.children[i], ignoreCollapsed });
}
insertedTreeIndex = nextTreeIndex;
const children = addAsFirstChild
? [newNode, ...parentNode.children]
: [...parentNode.children, newNode];
return {
...parentNode,
children,
};
},
});
if (!hasBeenAdded) {
throw new Error('No node found with the given key.');
}
return {
treeData: changedTreeData,
treeIndex: insertedTreeIndex,
};
};
const addNodeAtDepthAndIndex = ({ targetDepth, minimumTreeIndex, newNode, ignoreCollapsed, expandParent, isPseudoRoot = false, isLastChild, node, currentIndex, currentDepth, getNodeKey, path = [], }) => {
const selfPath = (n) => isPseudoRoot
? []
: [...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 > 0))) {
if (typeof node.children === 'function') {
throw new TypeError('Cannot add to children defined by a function');
}
else {
const extraNodeProps = expandParent ? { expanded: true } : {};
const nextNode = {
...node,
...extraNodeProps,
children: node.children ? [newNode, ...node.children] : [newNode],
};
return {
node: nextNode,
nextIndex: currentIndex + 2,
insertedTreeIndex: currentIndex + 1,
parentPath: selfPath(nextNode),
parentNode: isPseudoRoot ? undefined : 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, nextIndex: currentIndex + 1 };
}
// Scan over the children to see if there's a place among them that fulfills
// the minimumTreeIndex requirement
let childIndex = currentIndex + 1;
let insertedTreeIndex;
let insertIndex;
for (let 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 });
}
// If no valid indices to add the node were found
if (insertIndex === null || insertIndex === undefined) {
// 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, 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
const nextNode = {
...node,
children: [
...node.children.slice(0, insertIndex),
newNode,
...node.children.slice(insertIndex),
],
};
// Return node with successful insert result
return {
node: nextNode,
nextIndex: childIndex,
insertedTreeIndex,
parentPath: selfPath(nextNode),
parentNode: isPseudoRoot ? undefined : nextNode,
};
}
// Skip over nodes with no children or hidden children
if (!node.children ||
typeof node.children === 'function' ||
(node.expanded !== true && ignoreCollapsed && !isPseudoRoot)) {
return { node, nextIndex: currentIndex + 1 };
}
// Get all descendants
let insertedTreeIndex;
let pathFragment;
let parentNode;
let childIndex = currentIndex + 1;
let newChildren = node.children;
if (typeof newChildren !== 'function') {
newChildren = newChildren.map((child, i) => {
if (insertedTreeIndex !== null && insertedTreeIndex !== undefined) {
return child;
}
const mapResult = addNodeAtDepthAndIndex({
targetDepth,
minimumTreeIndex,
newNode,
ignoreCollapsed,
expandParent,
isLastChild: isLastChild && i === newChildren.length - 1,
node: child,
currentIndex: childIndex,
currentDepth: currentDepth + 1,
getNodeKey,
path: [], // Cannot determine the parent path until the children have been processed
});
if ('insertedTreeIndex' in mapResult) {
({
insertedTreeIndex,
parentNode,
parentPath: pathFragment,
} = mapResult);
}
childIndex = mapResult.nextIndex;
return mapResult.node;
});
}
const nextNode = { ...node, children: newChildren };
const result = {
node: nextNode,
nextIndex: childIndex,
};
if (insertedTreeIndex !== null && insertedTreeIndex !== undefined) {
result.insertedTreeIndex = insertedTreeIndex;
result.parentPath = [...selfPath(nextNode), ...pathFragment];
result.parentNode = parentNode;
}
return result;
};
const insertNode = ({ treeData, depth: targetDepth, minimumTreeIndex, newNode, getNodeKey, ignoreCollapsed = true, expandParent = false, }) => {
if (!treeData && targetDepth === 0) {
return {
treeData: [newNode],
treeIndex: 0,
path: [getNodeKey({ node: newNode, treeIndex: 0 })],
parentNode: undefined,
};
}
const insertResult = addNodeAtDepthAndIndex({
targetDepth,
minimumTreeIndex,
newNode,
ignoreCollapsed,
expandParent,
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.');
}
const treeIndex = insertResult.insertedTreeIndex;
return {
treeData: insertResult.node.children,
treeIndex,
path: [
...insertResult.parentPath,
getNodeKey({ node: newNode, treeIndex }),
],
parentNode: insertResult.parentNode,
};
};
const getFlatDataFromTree = ({ treeData, getNodeKey, ignoreCollapsed = true, }) => {
if (!treeData || treeData.length === 0) {
return [];
}
const flattened = [];
walk({
treeData,
getNodeKey,
ignoreCollapsed,
callback: (nodeInfo) => {
flattened.push(nodeInfo);
},
});
return flattened;
};
const getTreeFromFlatData = ({ flatData, getKey = (node) => node.id, getParentKey = (node) => node.parentId, rootKey = '0', }) => {
if (!flatData) {
return [];
}
const childrenToParents = {};
for (const child of flatData) {
const parentKey = getParentKey(child);
if (parentKey in childrenToParents) {
childrenToParents[parentKey].push(child);
}
else {
childrenToParents[parentKey] = [child];
}
}
if (!(rootKey in childrenToParents)) {
return [];
}
const trav = (parent) => {
const parentKey = getKey(parent);
if (parentKey in childrenToParents) {
return {
...parent,
children: childrenToParents[parentKey].map((child) => trav(child)),
};
}
return { ...parent };
};
return childrenToParents[rootKey].map((child) => trav(child));
};
const isDescendant = (older, younger) => {
return (!!older.children &&
typeof older.children !== 'function' &&
older.children.some((child) => child === younger || isDescendant(child, younger)));
};
const getDepth = (node, depth = 0) => {
if (!node.children) {
return depth;
}
if (typeof node.children === 'function') {
return depth + 1;
}
return node.children.reduce((deepest, child) => Math.max(deepest, getDepth(child, depth + 1)), depth);
};
const find = ({ getNodeKey, treeData, searchQuery, searchMethod, searchFocusOffset, expandAllMatchPaths = false, expandFocusMatchPaths = true, }) => {
let matchCount = 0;
const trav = ({ isPseudoRoot = false, node, currentIndex, path = [] }) => {
let matches = [];
let isSelfMatch = false;
let hasFocusMatch = false;
// The pseudo-root is not considered in the path
const selfPath = isPseudoRoot
? []
: [...path, getNodeKey({ node, treeIndex: currentIndex })];
const extraInfo = isPseudoRoot
? undefined
: {
path: selfPath,
treeIndex: currentIndex,
};
// Nodes with with children that aren't lazy
const 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({ ...extraInfo, node, 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;
}
let childIndex = currentIndex;
const newNode = { ...node };
if (hasChildren) {
// Get all descendants
newNode.children = newNode.children.map((child) => {
const 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 = [...matches, ...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((match) => ({
...match,
treeIndex: undefined,
}));
}
// 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 = [{ ...extraInfo, node: newNode }, ...matches];
}
return {
node: matches.length > 0 ? newNode : node,
matches,
hasFocusMatch,
treeIndex: childIndex,
};
};
const result = trav({
node: { children: treeData },
isPseudoRoot: true,
currentIndex: -1,
});
return {
matches: result.matches,
treeData: result.node.children,
};
};
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var lodash_isequal = {exports: {}};
/**
* Lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright JS Foundation and other contributors <https://js.foundation/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
lodash_isequal.exports;
(function (module, exports) {
/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1,
COMPARE_UNORDERED_FLAG = 2;
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
asyncTag = '[object AsyncFunction]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
genTag = '[object GeneratorFunction]',
mapTag = '[object Map]',
numberTag = '[object Number]',
nullTag = '[object Null]',
objectTag = '[object Object]',
promiseTag = '[object Promise]',
proxyTag = '[object Proxy]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
symbolTag = '[object Symbol]',
undefinedTag = '[object Undefined]',
weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to detect host constructors (Safari). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();
/** Detect free variable `exports`. */
var freeExports = exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports && freeGlobal.process;
/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
try {
return freeProcess && freeProcess.binding && freeProcess.binding('util');
} catch (e) {}
}());
/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
/**
* A specialized version of `_.filter` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {Array} Returns the new filtered array.
*/
function arrayFilter(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length,
resIndex = 0,
result = [];
while (++index < length) {
var value = array[index];
if (predicate(value, index, array)) {
result[resIndex++] = value;
}
}
return result;
}
/**
* Appends the elements of `values` to `array`.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to append.
* @returns {Array} Returns `array`.
*/
function arrayPush(array, values) {
var index = -1,
length = values.length,
offset = array.length;
while (++index < length) {
array[offset + index] = values[index];
}
return array;
}
/**
* A specialized version of `_.some` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {boolean} Returns `true` if any element passes the predicate check,
* else `false`.
*/
function arraySome(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (predicate(array[index], index, array)) {
return true;
}
}
return false;
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/**
* The base implementation of `_.unary` without support for storing metadata.
*
* @private
* @param {Function} func The function to cap arguments for.
* @returns {Function} Returns the new capped function.
*/
function baseUnary(func) {
return function(value) {
return func(value);
};
}
/**
* Checks if a `cache` value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function cacheHas(cache, key) {
return cache.has(key);
}
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function getValue(object, key) {
return object == null ? undefined : object[key];
}
/**
* Converts `map` to its key-value pairs.
*
* @private
* @param {Object} map The map to convert.
* @returns {Array} Returns the key-value pairs.
*/
function mapToArray(map) {
var index = -1,
result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [key, value];
});
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/**
* Converts `set` to an array of its values.
*
* @private
* @param {Object} set The set to convert.
* @returns {Array} Returns the values.
*/
function setToArray(set) {
var index = -1,
result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
/** Used for built-in method references. */
var arrayProto = Array.prototype,
funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to detect overreaching core-js shims. */
var coreJsData = root['__core-js_shared__'];
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to detect methods masquerading as native. */
var maskSrcKey = (function() {
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
return uid ? ('Symbol(src)_1.' + uid) : '';
}());
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;
/** Used to detect if a method is native. */
var reIsNative = RegExp('^' +
funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);
/** Built-in value references. */
var Buffer = moduleExports ? root.Buffer : undefined,
Symbol = root.Symbol,
Uint8Array = root.Uint8Array,
propertyIsEnumerable = objectProto.propertyIsEnumerable,
splice = arrayProto.splice,
symToStringTag = Symbol ? Symbol.toStringTag : undefined;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeGetSymbols = Object.getOwnPropertySymbols,
nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
nativeKeys = overArg(Object.keys, Object);
/* Built-in method references that are verified to be native. */
var DataView = getNative(root, 'DataView'),
Map = getNative(root, 'Map'),
Promise = getNative(root, 'Promise'),
Set = getNative(root, 'Set'),
WeakMap = getNative(root, 'WeakMap'),
nativeCreate = getNative(Object, 'create');
/** Used to detect maps, sets, and weakmaps. */
var dataViewCtorString = toSource(DataView),
mapCtorString = toSource(Map),
promiseCtorString = toSource(Promise),
setCtorString = toSource(Set),
weakMapCtorString = toSource(WeakMap);
/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol ? Symbol.prototype : undefined,
symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Hash(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/
function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
this.size = 0;
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function hashDelete(key) {
var result = this.has(key) && delete this.__data__[key];
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function hashGet(key) {
var data = this.__data__;
if (nativeCreate) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty.call(data, key) ? data[key] : undefined;
}
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function hashHas(key) {
var data = this.__data__;
return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
}
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/
function hashSet(key, value) {
var data = this.__data__;
this.size += this.has(key) ? 0 : 1;
data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
return this;
}
// Add methods to `Hash`.
Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/
function listCacheClear() {
this.__data__ = [];
this.size = 0;
}
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
return false;
}
var lastIndex = data.length - 1;
if (index == lastIndex) {
data.pop();
} else {
splice.call(data, index, 1);
}
--this.size;
return true;
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
++this.size;
data.push([key, value]);
} else {
data[index][1] = value;
}
return this;
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function MapCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/
function mapCacheClear() {
this.size = 0;
this.__data__ = {
'hash': new Hash,
'map': new (Map || ListCache),
'string': new Hash
};
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function mapCacheDelete(key) {
var result = getMapData(this, key)['delete'](key);
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function mapCacheGet(key) {
return getMapData(this, key).get(key);
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function mapCacheHas(key) {
return getMapData(this, key).has(key);
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/
function mapCacheSet(key, value) {
var data = getMapData(this, key),
size = data.size;
data.set(key, value);
this.size += data.size == size ? 0 : 1;
return this;
}
// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype['delete'] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;
/**
*
* Creates an array cache object to store unique values.
*
* @private
* @constructor
* @param {Array} [values] The values to cache.
*/
function SetCache(values) {
var index = -1,
length = values == null ? 0 : values.length;
this.__data__ = new MapCache;
while (++index < length) {
this.add(values[index]);
}
}
/**
* Adds `value` to the array cache.
*
* @private
* @name add
* @memberOf SetCache
* @alias push
* @param {*} value The value to cache.
* @returns {Object} Returns the cache instance.
*/
function setCacheAdd(value) {
this.__data__.set(value, HASH_UNDEFINED);
return this;
}
/**
* Checks if `value` is in the array cache.
*
* @private
* @name has
* @memberOf SetCache
* @param {*} value The value to search for.
* @returns {number} Returns `true` if `value` is found, else `false`.
*/
function setCacheHas(value) {
return this.__data__.has(value);
}
// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;
/**
* Creates a stack cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Stack(entries) {
var data = this.__data__ = new ListCache(entries);
this.size = data.size;
}
/**
* Removes all key-value entries from the stack.
*
* @private
* @name clear
* @memberOf Stack
*/
function stackClear() {
this.__data__ = new ListCache;
this.size = 0;
}
/**
* Removes `key` and its value from the stack.
*
* @private
* @name delete
* @memberOf Stack
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function stackDelete(key) {
var data = this.__data__,
result = data['delete'](key);
this.size = data.size;
return result;
}
/**
* Gets the stack value for `key`.
*
* @private
* @name get
* @memberOf Stack
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function stackGet(key) {
return this.__data__.get(key);
}
/**
* Checks if a stack value for `key` exists.
*
* @private
* @name has
* @memberOf Stack
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function stackHas(key) {
return this.__data__.has(key);
}
/**
* Sets the stack `key` to `value`.
*
* @private
* @name set
* @memberOf Stack
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the stack cache instance.
*/
function stackSet(key, value) {
var data = this.__data__;
if (data instanceof ListCache) {
var pairs = data.__data__;
if (!Map || (pairs.length < LARGE