@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
328 lines (327 loc) • 9.93 kB
JavaScript
"use client";
import { findTreeNode, getAllChildrenNodes, getChildrenNodesValues } from "./get-children-nodes-values/get-children-nodes-values.mjs";
import { getAllCheckedNodes } from "./get-all-checked-nodes/get-all-checked-nodes.mjs";
import { memoizedIsNodeChecked } from "./is-node-checked/is-node-checked.mjs";
import { memoizedIsNodeIndeterminate } from "./is-node-indeterminate/is-node-indeterminate.mjs";
import { useCallback, useMemo, useRef, useState } from "react";
import { useUncontrolled } from "@mantine/hooks";
//#region packages/@mantine/core/src/components/Tree/use-tree.ts
function getInitialTreeExpandedState(initialState, data, value, acc = {}) {
data.forEach((node) => {
acc[node.value] = node.value in initialState ? initialState[node.value] : node.value === value;
if (Array.isArray(node.children)) getInitialTreeExpandedState(initialState, node.children, value, acc);
});
return acc;
}
function getTreeExpandedState(data, expandedNodesValues) {
const state = getInitialTreeExpandedState({}, data, []);
if (expandedNodesValues === "*") {
const result = {};
const keys = Object.keys(state);
for (let i = 0; i < keys.length; i++) result[keys[i]] = true;
return result;
}
expandedNodesValues.forEach((node) => {
state[node] = true;
});
return state;
}
function getInitialCheckedState(initialState, data, checkStrictly) {
if (checkStrictly) return initialState;
const acc = [];
initialState.forEach((node) => acc.push(...getChildrenNodesValues(node, data)));
return Array.from(new Set(acc));
}
function getAllNodeValues(data) {
const acc = [];
for (const node of data) {
acc.push(node.value);
if (Array.isArray(node.children) && node.children.length > 0) acc.push(...getAllNodeValues(node.children));
}
return acc;
}
function useTree({ initialSelectedState = [], expandedState, initialCheckedState = [], checkedState, initialExpandedState = {}, selectedState, multiple = false, onNodeCollapse, onNodeExpand, onCheckedStateChange, onSelectedStateChange, onExpandedStateChange, onLoadChildren, checkStrictly = false } = {}) {
const [data, setData] = useState([]);
const [_expandedState, setExpandedState] = useUncontrolled({
value: expandedState,
defaultValue: initialExpandedState,
finalValue: {},
onChange: onExpandedStateChange
});
const [_selectedState, setSelectedState] = useUncontrolled({
value: selectedState,
defaultValue: initialSelectedState,
finalValue: [],
onChange: onSelectedStateChange
});
const [_checkedState, setCheckedState] = useUncontrolled({
value: checkedState,
defaultValue: initialCheckedState,
finalValue: [],
onChange: onCheckedStateChange
});
const [anchorNode, setAnchorNode] = useState(null);
const loadingNodesRef = useRef(/* @__PURE__ */ new Set());
const loadedNodesRef = useRef(/* @__PURE__ */ new Set());
const [loadingNodes, setLoadingNodes] = useState([]);
const [loadErrors, setLoadErrors] = useState({});
const initialize = useCallback((_data) => {
setExpandedState(getInitialTreeExpandedState(_expandedState, _data, _selectedState));
setCheckedState(getInitialCheckedState(_checkedState, _data, checkStrictly));
setData(_data);
}, [
_selectedState,
_checkedState,
_expandedState,
checkStrictly
]);
const loadNodeImpl = useCallback(async (value) => {
if (!onLoadChildren) return;
if (loadingNodesRef.current.has(value) || loadedNodesRef.current.has(value)) return;
loadingNodesRef.current.add(value);
setLoadingNodes(Array.from(loadingNodesRef.current));
setLoadErrors((prev) => {
if (!(value in prev)) return prev;
const next = { ...prev };
delete next[value];
return next;
});
try {
await onLoadChildren(value);
loadedNodesRef.current.add(value);
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
setLoadErrors((prev) => ({
...prev,
[value]: err
}));
} finally {
loadingNodesRef.current.delete(value);
setLoadingNodes(Array.from(loadingNodesRef.current));
}
}, [onLoadChildren]);
const tryLoadAsync = useCallback((value) => {
if (!onLoadChildren) return;
const node = findTreeNode(value, data);
if (node && node.hasChildren && !Array.isArray(node.children)) loadNodeImpl(value);
}, [
onLoadChildren,
data,
loadNodeImpl
]);
const toggleExpanded = useCallback((value) => {
const nextState = {
..._expandedState,
[value]: !_expandedState[value]
};
nextState[value] ? onNodeExpand?.(value) : onNodeCollapse?.(value);
if (nextState[value]) tryLoadAsync(value);
setExpandedState(nextState);
}, [
onNodeCollapse,
onNodeExpand,
_expandedState,
tryLoadAsync
]);
const collapse = useCallback((value) => {
if (_expandedState[value] !== false) onNodeCollapse?.(value);
setExpandedState({
..._expandedState,
[value]: false
});
}, [onNodeCollapse, _expandedState]);
const expand = useCallback((value) => {
if (_expandedState[value] !== true) onNodeExpand?.(value);
tryLoadAsync(value);
setExpandedState({
..._expandedState,
[value]: true
});
}, [
onNodeExpand,
_expandedState,
tryLoadAsync
]);
const expandAllNodes = useCallback(() => {
const nextState = { ..._expandedState };
Object.keys(nextState).forEach((key) => {
nextState[key] = true;
tryLoadAsync(key);
});
setExpandedState(nextState);
}, [_expandedState, tryLoadAsync]);
const collapseAllNodes = useCallback(() => {
const nextState = { ..._expandedState };
Object.keys(nextState).forEach((key) => {
nextState[key] = false;
});
setExpandedState(nextState);
}, [_expandedState]);
const toggleSelected = useCallback((value) => {
if (!multiple) {
if (_selectedState.includes(value)) {
setAnchorNode(null);
return [];
}
setAnchorNode(value);
return [value];
}
if (_selectedState.includes(value)) {
setAnchorNode(null);
return _selectedState.filter((item) => item !== value);
}
setAnchorNode(value);
setSelectedState([..._selectedState, value]);
}, [_selectedState]);
const select = useCallback((value) => {
setAnchorNode(value);
setSelectedState(multiple ? _selectedState.includes(value) ? _selectedState : [..._selectedState, value] : [value]);
}, [_selectedState]);
const deselect = useCallback((value) => {
anchorNode === value && setAnchorNode(null);
setSelectedState(_selectedState.filter((item) => item !== value));
}, [_selectedState]);
const clearSelected = useCallback(() => {
setSelectedState([]);
setAnchorNode(null);
}, []);
const checkNode = useCallback((value) => {
if (checkStrictly) {
if (!_checkedState.includes(value)) setCheckedState([..._checkedState, value]);
} else {
const checkedNodes = getChildrenNodesValues(value, data);
setCheckedState(Array.from(new Set([..._checkedState, ...checkedNodes])));
}
}, [
data,
_checkedState,
checkStrictly
]);
const uncheckNode = useCallback((value) => {
if (checkStrictly) setCheckedState(_checkedState.filter((item) => item !== value));
else {
const checkedNodes = getChildrenNodesValues(value, data);
setCheckedState(_checkedState.filter((item) => !checkedNodes.includes(item)));
}
}, [
data,
_checkedState,
checkStrictly
]);
const checkAllNodes = useCallback(() => {
if (checkStrictly) setCheckedState(getAllNodeValues(data));
else setCheckedState(getAllChildrenNodes(data));
}, [data, checkStrictly]);
const uncheckAllNodes = useCallback(() => {
setCheckedState([]);
}, []);
const getCheckedNodes = useCallback(() => {
if (checkStrictly) return _checkedState.map((value) => {
const node = findTreeNode(value, data);
return {
checked: true,
indeterminate: false,
value,
hasChildren: node ? Array.isArray(node.children) && node.children.length > 0 || !!node.hasChildren : false
};
});
return getAllCheckedNodes(data, _checkedState).result;
}, [
checkStrictly,
_checkedState,
data
]);
const isNodeChecked = useCallback((value) => {
if (checkStrictly) return _checkedState.includes(value);
return memoizedIsNodeChecked(value, data, _checkedState);
}, [
checkStrictly,
_checkedState,
data
]);
const isNodeIndeterminate = useCallback((value) => {
if (checkStrictly) return false;
return memoizedIsNodeIndeterminate(value, data, _checkedState);
}, [
checkStrictly,
_checkedState,
data
]);
const isNodeLoading = useCallback((value) => loadingNodes.includes(value), [loadingNodes]);
const getNodeLoadError = useCallback((value) => loadErrors[value] || null, [loadErrors]);
const invalidateNode = useCallback((value) => {
loadedNodesRef.current.delete(value);
setLoadErrors((prev) => {
if (!(value in prev)) return prev;
const next = { ...prev };
delete next[value];
return next;
});
}, []);
return useMemo(() => ({
checkStrictly,
multiple,
expandedState: _expandedState,
selectedState: _selectedState,
checkedState: _checkedState,
anchorNode,
initialize,
toggleExpanded,
collapse,
expand,
expandAllNodes,
collapseAllNodes,
setExpandedState,
checkNode,
uncheckNode,
checkAllNodes,
uncheckAllNodes,
setCheckedState,
toggleSelected,
select,
deselect,
clearSelected,
setSelectedState,
getCheckedNodes,
isNodeChecked,
isNodeIndeterminate,
isNodeLoading,
getNodeLoadError,
loadNode: loadNodeImpl,
invalidateNode
}), [
checkStrictly,
multiple,
_expandedState,
_selectedState,
_checkedState,
anchorNode,
initialize,
toggleExpanded,
collapse,
expand,
expandAllNodes,
collapseAllNodes,
setExpandedState,
checkNode,
uncheckNode,
checkAllNodes,
uncheckAllNodes,
setCheckedState,
toggleSelected,
select,
deselect,
clearSelected,
setSelectedState,
getCheckedNodes,
isNodeChecked,
isNodeIndeterminate,
isNodeLoading,
getNodeLoadError,
loadNodeImpl,
invalidateNode
]);
}
//#endregion
export { getTreeExpandedState, useTree };
//# sourceMappingURL=use-tree.mjs.map