UNPKG

@flasham/react-sortable-tree

Version:

Drag-and-drop sortable component for nested data and hierarchies

1,580 lines (1,487 loc) 204 kB
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