123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- /**
- * Part of the utils function implementation process reference
- * https://github.com/react-component/tree/blob/master/src/util.tsx
- */
- import { difference, uniq, max, isObject, isNull, isUndefined, isEmpty, pick, get, omit } from 'lodash';
- export interface KeyEntities {
- [x: string]: any
- }
- export interface TreeDataSimpleJson {
- [x: string]: string | TreeDataSimpleJson
- }
- export interface NodeData {
- key: any;
- label: any;
- value: any;
- children?: any
- }
- export interface KeyMapProps {
- key?: string;
- label?: string;
- value?: string;
- disabled?: string;
- children?: string;
- isLeaf?: string;
- icon?: string
- }
- const DRAG_OFFSET = 0.45;
- function getPosition(level: any, index: any) {
- return `${level}-${index}`;
- }
- function isValid(val: any) {
- return !isNull(val) && !isUndefined(val);
- }
- /**
- * Flat nest tree data into flatten list. This is used for virtual list render.
- * @param treeNodeList Origin data node list
- * @param expandedKeys
- * @param filteredShownKeys
- * need expanded keys, provides `true` means all expanded
- */
- export function flattenTreeData(treeNodeList: any[], expandedKeys: Set<string>, keyMaps: KeyMapProps, filteredShownKeys: boolean | Set<any> = false) {
- const flattenList: any[] = [];
- const filterSearch = Boolean(filteredShownKeys);
- const realKeyName = get(keyMaps, 'key', 'key');
- const realChildrenName = get(keyMaps, 'children', 'children');
- function flatten(list: any[], parent: any = null) {
- return list.map((treeNode, index) => {
- const pos = getPosition(parent ? parent.pos : '0', index);
- const mergedKey = treeNode[realKeyName];
- const otherData = {};
- if (keyMaps) {
- Object.entries(omit(keyMaps, 'children')).forEach(([key, value]) => {
- const result = treeNode[value as string];
- !isUndefined(result) && (otherData[key] = result);
- });
- }
- // Add FlattenDataNode into list
- const flattenNode: any = {
- ...pick(treeNode, ['key', 'label', 'value', 'icon', 'disabled', 'isLeaf']),
- ...otherData,
- parent,
- pos,
- children: null,
- data: treeNode,
- _innerDataTag: true,
- };
- const isBooleanFilteredShownKeys = typeof filteredShownKeys === 'boolean';
- if (!filterSearch || (!isBooleanFilteredShownKeys && filteredShownKeys.has(mergedKey))) {
- flattenList.push(flattenNode);
- }
- // Loop treeNode children
- if (expandedKeys.has(mergedKey) && (!filterSearch || (!isBooleanFilteredShownKeys && filteredShownKeys.has(mergedKey)))) {
- flattenNode.children = flatten(treeNode[realChildrenName] || [], flattenNode);
- } else {
- flattenNode.children = [];
- }
- return flattenNode;
- });
- }
- flatten(treeNodeList);
- return flattenList;
- }
- export function convertJsonToData(treeJson: TreeDataSimpleJson) {
- const treeData: any[] = [];
- const traverseNode = (key: string, children: any, path: any, res: any[]) => {
- const currPath = [...path, key];
- const itemKey = currPath.join('-');
- const newNode: NodeData = {
- key: itemKey,
- label: key,
- value: children,
- };
- if (isObject(children)) {
- const newChildren: any[] = [];
- Object.entries(children).forEach(c => {
- traverseNode(c[0], c[1], currPath, newChildren);
- });
- newNode.children = newChildren;
- }
- res.push(newNode);
- };
- Object.entries(treeJson).forEach(item => traverseNode(item[0], item[1], [], treeData));
- return treeData;
- }
- /**
- * Traverse all the data by `treeData`.
- */
- export function traverseDataNodes(treeNodes: any[], callback: (data: any) => void, keyMaps: KeyMapProps) {
- const realKeyName = get(keyMaps, 'key', 'key');
- const realChildrenName = get(keyMaps, 'children', 'children');
- const processNode = (node: any, ind?: number, parent?: any) => {
- const children = node ? node[realChildrenName] : treeNodes;
- const pos = node ? getPosition(parent.pos, ind) : '0';
- // Process node if is not root
- if (node) {
- const nodeKey = get(node, realKeyName, null);
- const data = {
- data: { ...node },
- ind,
- pos,
- key: nodeKey !== null ? nodeKey : pos,
- parentPos: parent.node ? parent.pos : null,
- level: Number(parent.level) + 1,
- };
- callback(data);
- }
- // Process children node
- if (children) {
- children.forEach((subNode: any, subIndex: number) => {
- processNode(subNode, subIndex, {
- node,
- pos,
- level: parent ? Number(parent.level) + 1 : -1,
- });
- });
- }
- };
- processNode(null);
- }
- /* Convert data to entities map */
- export function convertDataToEntities(dataNodes: any[], keyMaps?: KeyMapProps) {
- const posEntities = {};
- const keyEntities = {};
- const valueEntities = {};
- const wrapper = {
- posEntities,
- keyEntities,
- valueEntities,
- };
- const realValueName = get(keyMaps, 'value', 'value');
- traverseDataNodes(dataNodes, (data: any) => {
- const { pos, key, parentPos } = data;
- const entity = { ...data };
- const value = get(entity, `data.${realValueName}`, null);
- if (value !== null) {
- valueEntities[value] = key;
- }
- posEntities[pos] = entity;
- keyEntities[key] = entity;
- // Fill children
- entity.parent = posEntities[parentPos];
- if (entity.parent) {
- entity.parent.children = entity.parent.children || [];
- entity.parent.children.push(entity);
- }
- }, keyMaps);
- return wrapper;
- }
- /* Get key by value */
- export function findKeysForValues(valueList: any, valueEntities: any, isMultiple = false) {
- if (!isValid(valueList)) {
- return [];
- }
- if (!isMultiple && Array.isArray(valueList)) {
- valueList = valueList.length ? [valueList[0]] : [];
- } else if (!Array.isArray(valueList)) {
- valueList = [valueList];
- }
- if (isEmpty(valueEntities)) {
- return valueList;
- }
- const res: any[] = [];
- valueList.forEach((val: string) => {
- if (val in valueEntities) {
- res.push(valueEntities[val]);
- } else {
- // if val not in valueEntities, then value push to keys array
- val && res.push(val);
- }
- });
- return res;
- }
- export function findDescendantKeys(selectedKeys: string[], options: KeyEntities, self = true) {
- const res: string[] = [];
- const findChild = (item: any) => {
- if (!item) {
- return;
- }
- const { children } = item;
- const hasChildren = isValid(children);
- if (hasChildren) {
- children.forEach((child: any) => {
- res.push(child.key);
- findChild(options[child.key]);
- });
- }
- };
- selectedKeys.forEach(item => {
- if (self) {
- res.push(item);
- }
- findChild(options[item]);
- });
- return res;
- }
- export function findChildKeys(keys: string[], options: any, omitKeys: any[] = []) {
- const res: any[] = [];
- keys &&
- keys.forEach(key => {
- const opts = options[key];
- opts &&
- opts.children &&
- opts.children.forEach((child: any) => {
- if (!omitKeys.length || !omitKeys.includes(child.key)) {
- res.push(child.key);
- }
- });
- });
- return res;
- }
- /* istanbul ignore next */
- export function findLeafKeys(keys: string[], options: any) {
- const res: any[] = [];
- const findChild = (item: any) => {
- if (!item) {
- return;
- }
- const { children } = item;
- const isLeaf = !isValid(children);
- if (isLeaf) {
- res.push(item.key);
- } else {
- children.forEach((child: any) => {
- findChild(options[child.key]);
- });
- }
- };
- keys.forEach(item => {
- findChild(options[item]);
- });
- return res;
- }
- export function findSiblingKeys(selectedKeys: string[], options: any, self = true) {
- const par: any[] = [];
- selectedKeys.forEach(item => {
- if (options[item] && options[item].parent) {
- par.push(options[item].parent.key);
- }
- });
- const res = findChildKeys(uniq(par), options, self ? [] : selectedKeys);
- return res;
- }
- export function findAncestorKeys(selectedKeys: string[], options: any, self = true) {
- const res: any[] = [];
- // Recursively find the parent element
- const findPar = (item: any) => {
- if (item.parent) {
- res.push(item.parent.key);
- findPar(item.parent);
- }
- };
- selectedKeys.forEach(item => {
- options[item] && findPar(options[item]);
- if (self) {
- res.push(item);
- }
- });
- return res;
- }
- function getSortedKeyList(keyList: any[], keyEntities: KeyEntities) {
- const levelMap = {};
- keyList.forEach(key => {
- if (!keyEntities[key]) {
- return;
- }
- const { level } = keyEntities[key];
- if (levelMap[level]) {
- levelMap[level].push(key);
- } else {
- levelMap[level] = [key];
- }
- });
- return levelMap;
- }
- export function calcCheckedKeys(values: any, keyEntities: KeyEntities) {
- const keyList = Array.isArray(values) ? values : [values];
- const descendantKeys = findDescendantKeys(keyList, keyEntities, true);
- /**
- * Recursively find the parent element. Because the incoming nodes are all checked,
- * their descendants must be checked. That is to say, if the descendant nodes have
- * disabled+unchecked nodes, their ancestor nodes will definitely not be checked
- */
- const checkedKeys = new Set([...descendantKeys]);
- let halfCheckedKeys = new Set([]);
- let visited: any[] = [];
- const levelMap: {[key: number]: string[]} = getSortedKeyList(keyList, keyEntities);
- const calcCurrLevel = (node: any) => {
- const { key, parent, level } = node;
- // If the node does not have a parent node, or the node has been processed just now, no processing is done
- if (!parent || visited.includes(key)) {
- return;
- }
- const siblingKeys = findSiblingKeys([key], keyEntities);
- // visited for caching to avoid double counting
- visited = [...visited, ...siblingKeys];
- const allChecked = siblingKeys.every((siblingKey: string) => checkedKeys.has(siblingKey));
- if (!allChecked) {
- const ancestorKeys = findAncestorKeys([key], keyEntities, false);
- halfCheckedKeys = new Set([...halfCheckedKeys, ...ancestorKeys]);
- } else {
- checkedKeys.add(parent.key);
- // IMPORTANT! parent level may not exist in original level map; if add to the end directly may destroy the hierarchical order
- if (level - 1 in levelMap && level) {
- levelMap[level - 1].push(parent.key);
- } else {
- levelMap[level - 1] = [parent.key];
- }
- }
- };
- // Loop keyList from deepest Level to topLevel, bottom up
- while (!isEmpty(levelMap)) {
- const maxLevel = max(Object.keys(levelMap).map(key => Number(key)));
- levelMap[maxLevel].forEach((key: string) => calcCurrLevel(keyEntities[key]));
- delete levelMap[maxLevel];
- }
- return {
- checkedKeys,
- halfCheckedKeys,
- };
- }
- /* Calculate the expanded node by key */
- export function calcExpandedKeys(keyList: any[] = [], keyEntities: KeyEntities, autoExpandParent = true) {
- if (!Array.isArray(keyList)) {
- keyList = [keyList];
- }
- if (autoExpandParent) {
- const ancestorKeys = findAncestorKeys(keyList, keyEntities, true);
- return new Set(ancestorKeys);
- }
- return new Set(keyList);
- }
- /* Calculate the expanded node by value */
- export function calcExpandedKeysForValues(value: any, keyEntities: KeyEntities, isMultiple: boolean, valueEntities: any) {
- const keys = findKeysForValues(value, valueEntities, isMultiple);
- return new Set(findAncestorKeys(keys, keyEntities, false));
- }
- export function calcMotionKeys(oldKeySet: Set<string>, newKeySet: Set<string>, keyEntities: KeyEntities) {
- let motionType = 'show';
- const oldKeys = [...oldKeySet];
- const newKeys = [...newKeySet];
- if (Math.abs(oldKeys.length - newKeys.length) !== 1) {
- return { motionType, motionKeys: [] };
- }
- let diffKeys = [];
- if (oldKeys.length > newKeys.length) {
- motionType = 'hide';
- diffKeys = difference(oldKeys, newKeys);
- } else {
- diffKeys = difference(newKeys, oldKeys);
- }
- return {
- motionType: diffKeys.length === 1 ? motionType : 'show',
- motionKeys: diffKeys.length === 1 ? findDescendantKeys(diffKeys, keyEntities, false) : [],
- };
- }
- /**
- * @returns whether option includes sugInput.
- * When filterTreeNode is a function,returns the result of filterTreeNode which called with (sugInput, target, option).
- * The filteredPath parameter will only be passed in when the Cascader calls the filter function
- */
- export function filter(sugInput: string, option: any, filterTreeNode: any, filterProps: any, filteredPath?: string) {
- if (!filterTreeNode) {
- return true;
- }
- let filterFn = filterTreeNode;
- let target = filteredPath ?? option;
- if (typeof filterTreeNode === 'boolean') {
- filterFn = (targetVal: string, val: any) => {
- const input = targetVal.toLowerCase();
- return val
- .toString()
- .toLowerCase()
- .includes(input);
- };
- }
- if (filterProps) {
- target = option[filterProps];
- }
- return filterFn(sugInput, target, option);
- }
- export function normalizedArr(val: any) {
- if (!Array.isArray(val)) {
- return [val];
- } else {
- return val;
- }
- }
- // flag is used to determine whether to return when the key does not belong to the keys in keyEntities
- // export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false) {
- export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false, flag?: boolean) {
- const res: string[] = [];
- const keyListSet = new Set(keyList);
- if (!leafOnly) {
- keyList.forEach((key: string) => {
- if (!keyEntities[key]) {
- if (flag) {
- res.push(key);
- }
- return;
- }
- const { parent } = keyEntities[key];
- if (parent && keyListSet.has(parent.key)) {
- return;
- }
- res.push(key);
- });
- } else {
- keyList.forEach(key => {
- if (keyEntities[key] && !isValid(keyEntities[key].children)) {
- res.push(key);
- }
- // when key is not in keyEntities, if flag is true, key should be push in res
- if (!keyEntities[key] && flag) {
- res.push(key);
- }
- });
- }
- return res;
- }
- export function getMotionKeys(eventKey: string, expandedKeys: Set<string>, keyEntities: KeyEntities) {
- const res: any[] = [];
- const getChild = (itemKey: string) => {
- keyEntities[itemKey].children &&
- keyEntities[itemKey].children.forEach((item: any) => {
- const { key } = item;
- res.push(key);
- if (expandedKeys.has(key)) {
- getChild(key);
- }
- });
- };
- getChild(eventKey);
- return res;
- }
- export function calcCheckedKeysForChecked(key: string, keyEntities: KeyEntities, checkedKeys: Set<string>, halfCheckedKeys: Set<string>) {
- const descendantKeys = findDescendantKeys([key], keyEntities, true);
- const nodeItem = keyEntities[key];
- checkedKeys = new Set([...checkedKeys, key]);
- const calcCurrLevel = (node: any) => {
- if (!node.parent) {
- return;
- }
- const { key } = node;
- const siblingKeys = findSiblingKeys([key], keyEntities);
- const allChecked = siblingKeys.every(key => checkedKeys.has(key));
- if (!allChecked) {
- const ancestorKeys = findAncestorKeys([key], keyEntities, false);
- halfCheckedKeys = new Set([...halfCheckedKeys, ...ancestorKeys]);
- } else {
- const par = node.parent;
- checkedKeys.add(par.key);
- calcCurrLevel(par);
- }
- };
- calcCurrLevel(nodeItem);
- return {
- checkedKeys: new Set([...checkedKeys, ...descendantKeys]),
- halfCheckedKeys,
- };
- }
- export function calcCheckedKeysForUnchecked(key: string, keyEntities: KeyEntities, checkedKeys: Set<string>, halfCheckedKeys: Set<string>) {
- const descendantKeys = findDescendantKeys([key], keyEntities, true);
- const nodeItem = keyEntities[key];
- descendantKeys.forEach(descendantKey => {
- if (checkedKeys.has(descendantKey)) {
- checkedKeys.delete(descendantKey);
- }
- if (halfCheckedKeys.has(descendantKey)) {
- halfCheckedKeys.delete(descendantKey);
- }
- });
- const calcCurrLevel = (node: any) => {
- const par = node.parent;
- // no parent
- if (!par) {
- return;
- }
- // Has a parent node, and the parent node is not checked or halfChecked
- if (!checkedKeys.has(par.key) && !halfCheckedKeys.has(par.key)) {
- return;
- }
- // Has a parent node, and the parent node is checked or halfChecked
- const { key } = node;
- const siblingKeys = findSiblingKeys([key], keyEntities);
- const anyChecked = siblingKeys.some(key => checkedKeys.has(key) || halfCheckedKeys.has(key));
- const ancestorKeys = findAncestorKeys([key], keyEntities, false);
- // If there is checked or halfChecked in the sibling node, you need to change the parent node to halfChecked
- if (anyChecked) {
- ancestorKeys.forEach(itemKey => {
- if (checkedKeys.has(itemKey)) {
- checkedKeys.delete(itemKey);
- halfCheckedKeys.add(itemKey);
- }
- });
- // If there is no checked or halfChecked in the sibling node, you need to change the parent node to unchecked
- } else {
- if (checkedKeys.has(par.key)) {
- checkedKeys.delete(par.key);
- }
- if (halfCheckedKeys.has(par.key)) {
- halfCheckedKeys.delete(par.key);
- }
- calcCurrLevel(par);
- }
- };
- nodeItem && calcCurrLevel(nodeItem);
- return {
- checkedKeys,
- halfCheckedKeys,
- };
- }
- export function filterTreeData(info: any) {
- const {
- showFilteredOnly,
- keyEntities,
- inputValue,
- treeData,
- filterTreeNode,
- filterProps,
- prevExpandedKeys,
- keyMaps
- } = info;
- let filteredOptsKeys = [];
- filteredOptsKeys = Object.values(keyEntities)
- .filter((item: any) => filter(inputValue, item.data, filterTreeNode, filterProps))
- .map((item: any) => item.key);
- let expandedOptsKeys = findAncestorKeys(filteredOptsKeys, keyEntities, false);
- if (prevExpandedKeys.length) {
- const prevExpandedValidKeys = prevExpandedKeys.filter((key: string) => Boolean(keyEntities[key]));
- expandedOptsKeys = expandedOptsKeys.concat(prevExpandedValidKeys);
- }
- const shownChildKeys = findDescendantKeys(filteredOptsKeys, keyEntities, true);
- const filteredShownKeys = new Set([...shownChildKeys, ...expandedOptsKeys]);
- const flattenNodes = flattenTreeData(treeData, new Set(expandedOptsKeys), keyMaps, showFilteredOnly && filteredShownKeys);
- return {
- flattenNodes,
- filteredKeys: new Set(filteredOptsKeys),
- filteredExpandedKeys: new Set(expandedOptsKeys),
- filteredShownKeys,
- };
- }
- // return data.value if data.value exist else fall back to key
- export function getValueOrKey(data: any, keyMaps?: KeyMapProps) {
- const valueName = get(keyMaps, 'value', 'value');
- const keyName = get(keyMaps, 'key', 'key');
- if (Array.isArray(data)) {
- return data.map(item => get(item, valueName, item[keyName]));
- }
- return get(data, valueName, data[keyName]);
- }
- /* Convert value to string */
- export function normalizeValue(value: any, withObject: boolean, keyMaps?: KeyMapProps) {
- if (withObject && isValid(value)) {
- return getValueOrKey(value, keyMaps);
- } else {
- return value;
- }
- }
- export function updateKeys(keySet: Set<string> | string[], keyEntities: KeyEntities) {
- const keyArr = [...keySet];
- return keyArr.filter(key => key in keyEntities);
- }
- export function calcDisabledKeys(keyEntities: KeyEntities, keyMaps?: KeyMapProps) {
- const disabledName = get(keyMaps, 'disabled', 'disabled');
- const disabledKeys = Object.keys(keyEntities).filter(key => keyEntities[key].data[disabledName]);
- const { checkedKeys } = calcCheckedKeys(disabledKeys, keyEntities);
- return checkedKeys;
- }
- export function calcDropRelativePosition(event: any, treeNode: any) {
- const { clientY } = event;
- const { top, bottom, height } = treeNode.nodeInstance.getBoundingClientRect();
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- if (clientY <= top + height * DRAG_OFFSET) {
- return -1;
- }
- if (clientY >= bottom - height * DRAG_OFFSET) {
- return 1;
- }
- return 0;
- }
- export function getDragNodesKeys(key: string, keyEntities: KeyEntities) {
- return findDescendantKeys([key], keyEntities, true);
- }
- export function calcDropActualPosition(pos: string, relativeDropPos: any) {
- const posArr = pos.split('-');
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- return relativeDropPos + Number(posArr[posArr.length - 1]);
- }
|