@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
168 lines (167 loc) • 6.88 kB
JavaScript
import { getFirstNode, getLastNode, getNextNode, populateInstance } from "../utils.js";
import * as React from "react";
//#region src/TreeView/internals/hooks/plugins/useTreeViewSelection.ts
/**
* This is used to determine the start and end of a selection range so
* we can get the nodes between the two border nodes.
*
* It finds the nodes' common ancestor using
* a naive implementation of a lowest common ancestor algorithm
* (https://en.wikipedia.org/wiki/Lowest_common_ancestor).
* Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB
* so we can compare their indexes to work out which node comes first in a depth first search.
* (https://en.wikipedia.org/wiki/Depth-first_search)
*
* Another way to put it is which node is shallower in a trémaux tree
* https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree
*/
var findOrderInTremauxTree = (instance, nodeAId, nodeBId) => {
if (nodeAId === nodeBId) return [nodeAId, nodeBId];
const nodeA = instance.getNode(nodeAId);
const nodeB = instance.getNode(nodeBId);
if (nodeA.parentId === nodeB.id || nodeB.parentId === nodeA.id) return nodeB.parentId === nodeA.id ? [nodeA.id, nodeB.id] : [nodeB.id, nodeA.id];
const aFamily = [nodeA.id];
const bFamily = [nodeB.id];
let aAncestor = nodeA.parentId;
let bAncestor = nodeB.parentId;
let aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1;
let bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1;
let continueA = true;
let continueB = true;
while (!bAncestorIsCommon && !aAncestorIsCommon) {
if (continueA) {
aFamily.push(aAncestor);
aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1;
continueA = aAncestor !== null;
if (!aAncestorIsCommon && continueA) aAncestor = instance.getNode(aAncestor).parentId;
}
if (continueB && !aAncestorIsCommon) {
bFamily.push(bAncestor);
bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1;
continueB = bAncestor !== null;
if (!bAncestorIsCommon && continueB) bAncestor = instance.getNode(bAncestor).parentId;
}
}
const commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor;
const ancestorFamily = instance.getChildrenIds(commonAncestor);
const aSide = aFamily[aFamily.indexOf(commonAncestor) - 1];
const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1];
return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [nodeAId, nodeBId] : [nodeBId, nodeAId];
};
var useTreeViewSelection = ({ instance, params, models }) => {
const lastSelectedNode = React.useRef(null);
const lastSelectionWasRange = React.useRef(false);
const currentRangeSelection = React.useRef([]);
const isNodeSelected = (nodeId) => Array.isArray(models.selected.value) ? models.selected.value.indexOf(nodeId) !== -1 : models.selected.value === nodeId;
const selectNode = (event, nodeId, multiple = false) => {
if (params.disableSelection) return;
if (multiple) {
if (Array.isArray(models.selected.value)) {
let newSelected;
if (models.selected.value.indexOf(nodeId) !== -1) newSelected = models.selected.value.filter((id) => id !== nodeId);
else newSelected = [nodeId].concat(models.selected.value);
if (params.onNodeSelect) params.onNodeSelect(event, newSelected);
models.selected.setValue(newSelected);
}
} else {
const newSelected = params.multiSelect ? [nodeId] : nodeId;
if (params.onNodeSelect) params.onNodeSelect(event, newSelected);
models.selected.setValue(newSelected);
}
lastSelectedNode.current = nodeId;
lastSelectionWasRange.current = false;
currentRangeSelection.current = [];
};
const getNodesInRange = (nodeAId, nodeBId) => {
const [first, last] = findOrderInTremauxTree(instance, nodeAId, nodeBId);
const nodes = [first];
let current = first;
while (current !== last) {
current = getNextNode(instance, current);
nodes.push(current);
}
return nodes;
};
const handleRangeArrowSelect = (event, nodes) => {
let base = models.selected.value.slice();
const { start, next, current } = nodes;
if (!next || !current) return;
if (currentRangeSelection.current.indexOf(current) === -1) currentRangeSelection.current = [];
if (lastSelectionWasRange.current) if (currentRangeSelection.current.indexOf(next) !== -1) {
base = base.filter((id) => id === start || id !== current);
currentRangeSelection.current = currentRangeSelection.current.filter((id) => id === start || id !== current);
} else {
base.push(next);
currentRangeSelection.current.push(next);
}
else {
base.push(next);
currentRangeSelection.current.push(current, next);
}
if (params.onNodeSelect) params.onNodeSelect(event, base);
models.selected.setValue(base);
};
const handleRangeSelect = (event, nodes) => {
let base = models.selected.value.slice();
const { start, end } = nodes;
if (lastSelectionWasRange.current) base = base.filter((id) => currentRangeSelection.current.indexOf(id) === -1);
let range = getNodesInRange(start, end);
range = range.filter((node) => !instance.isNodeDisabled(node));
currentRangeSelection.current = range;
let newSelected = base.concat(range);
newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i);
if (params.onNodeSelect) params.onNodeSelect(event, newSelected);
models.selected.setValue(newSelected);
};
const selectRange = (event, nodes, stacked = false) => {
if (params.disableSelection) return;
const { start = lastSelectedNode.current, end, current } = nodes;
if (stacked) handleRangeArrowSelect(event, {
start,
next: end,
current
});
else if (start != null && end != null) handleRangeSelect(event, {
start,
end
});
lastSelectionWasRange.current = true;
};
const rangeSelectToFirst = (event, nodeId) => {
if (!lastSelectedNode.current) lastSelectedNode.current = nodeId;
const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId;
instance.selectRange(event, {
start,
end: getFirstNode(instance)
});
};
const rangeSelectToLast = (event, nodeId) => {
if (!lastSelectedNode.current) lastSelectedNode.current = nodeId;
const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId;
instance.selectRange(event, {
start,
end: getLastNode(instance)
});
};
populateInstance(instance, {
isNodeSelected,
selectNode,
selectRange,
rangeSelectToLast,
rangeSelectToFirst
});
return { getRootProps: () => ({ "aria-multiselectable": params.multiSelect }) };
};
useTreeViewSelection.models = { selected: {
controlledProp: "selected",
defaultProp: "defaultSelected"
} };
var DEFAULT_SELECTED = [];
useTreeViewSelection.getDefaultizedParams = (params) => ({
...params,
disableSelection: params.disableSelection ?? false,
multiSelect: params.multiSelect ?? false,
defaultSelected: params.defaultSelected ?? (params.multiSelect ? DEFAULT_SELECTED : null)
});
//#endregion
export { useTreeViewSelection };