@mui/x-tree-view
Version:
The community edition of the MUI X Tree View components.
163 lines (157 loc) • 5.9 kB
JavaScript
'use client';
import { useTreeViewContext } from "../../internals/TreeViewProvider/index.js";
import { useTreeViewLabel } from "../../internals/plugins/useTreeViewLabel/index.js";
import { hasPlugin } from "../../internals/utils/plugins.js";
import { useSelector } from "../../internals/hooks/useSelector.js";
import { selectorIsItemExpandable, selectorIsItemExpanded } from "../../internals/plugins/useTreeViewExpansion/useTreeViewExpansion.selectors.js";
import { selectorIsItemFocused } from "../../internals/plugins/useTreeViewFocus/useTreeViewFocus.selectors.js";
import { selectorIsItemDisabled } from "../../internals/plugins/useTreeViewItems/useTreeViewItems.selectors.js";
import { selectorIsItemSelected, selectorIsMultiSelectEnabled } from "../../internals/plugins/useTreeViewSelection/useTreeViewSelection.selectors.js";
import { selectorGetTreeItemError, selectorIsItemLoading, selectorIsLazyLoadingEnabled } from "../../internals/plugins/useTreeViewLazyLoading/useTreeViewLazyLoading.selectors.js";
import { selectorIsItemBeingEdited, selectorIsItemEditable } from "../../internals/plugins/useTreeViewLabel/useTreeViewLabel.selectors.js";
/**
* Plugins that need to be present in the Tree View in order for `useTreeItemUtils` to work correctly.
*/
/**
* Plugins that `useTreeItemUtils` can use if they are present, but are not required.
*/
export const itemHasChildren = reactChildren => {
if (Array.isArray(reactChildren)) {
return reactChildren.length > 0 && reactChildren.some(itemHasChildren);
}
return Boolean(reactChildren);
};
export const useTreeItemUtils = ({
itemId,
children
}) => {
const {
instance,
store,
publicAPI
} = useTreeViewContext();
const isItemExpandable = useSelector(store, selectorIsItemExpandable, itemId);
const isLazyLoadingEnabled = useSelector(store, selectorIsLazyLoadingEnabled);
const isMultiSelectEnabled = useSelector(store, selectorIsMultiSelectEnabled);
const loading = useSelector(store, state => isLazyLoadingEnabled ? selectorIsItemLoading(state, itemId) : false);
const error = useSelector(store, state => isLazyLoadingEnabled ? Boolean(selectorGetTreeItemError(state, itemId)) : false);
const isExpandable = itemHasChildren(children) || isItemExpandable;
const isExpanded = useSelector(store, selectorIsItemExpanded, itemId);
const isFocused = useSelector(store, selectorIsItemFocused, itemId);
const isSelected = useSelector(store, selectorIsItemSelected, itemId);
const isDisabled = useSelector(store, selectorIsItemDisabled, itemId);
const isEditing = useSelector(store, selectorIsItemBeingEdited, itemId);
const isEditable = useSelector(store, selectorIsItemEditable, itemId);
const status = {
expandable: isExpandable,
expanded: isExpanded,
focused: isFocused,
selected: isSelected,
disabled: isDisabled,
editing: isEditing,
editable: isEditable,
loading,
error
};
const handleExpansion = event => {
if (status.disabled) {
return;
}
if (!status.focused) {
instance.focusItem(event, itemId);
}
const multiple = isMultiSelectEnabled && (event.shiftKey || event.ctrlKey || event.metaKey);
// If already expanded and trying to toggle selection don't close
if (status.expandable && !(multiple && selectorIsItemExpanded(store.value, itemId))) {
// make sure the children selection is propagated again
instance.setItemExpansion({
event,
itemId
});
}
};
const handleSelection = event => {
if (status.disabled) {
return;
}
if (!status.focused && !status.editing) {
instance.focusItem(event, itemId);
}
const multiple = isMultiSelectEnabled && (event.shiftKey || event.ctrlKey || event.metaKey);
if (multiple) {
if (event.shiftKey) {
instance.expandSelectionRange(event, itemId);
} else {
instance.setItemSelection({
event,
itemId,
keepExistingSelection: true
});
}
} else {
instance.setItemSelection({
event,
itemId,
shouldBeSelected: true
});
}
};
const handleCheckboxSelection = event => {
const hasShift = event.nativeEvent.shiftKey;
if (isMultiSelectEnabled && hasShift) {
instance.expandSelectionRange(event, itemId);
} else {
instance.setItemSelection({
event,
itemId,
keepExistingSelection: isMultiSelectEnabled,
shouldBeSelected: event.target.checked
});
}
};
const toggleItemEditing = () => {
if (!hasPlugin(instance, useTreeViewLabel)) {
return;
}
if (isEditing) {
instance.setEditedItem(null);
} else {
instance.setEditedItem(itemId);
}
};
const handleSaveItemLabel = (event, newLabel) => {
if (!hasPlugin(instance, useTreeViewLabel)) {
return;
}
// As a side effect of `instance.focusItem` called here and in `handleCancelItemLabelEditing` the `labelInput` is blurred
// The `onBlur` event is triggered, which calls `handleSaveItemLabel` again.
// To avoid creating an unwanted behavior we need to check if the item is being edited before calling `updateItemLabel`
if (selectorIsItemBeingEdited(store.value, itemId)) {
instance.updateItemLabel(itemId, newLabel);
toggleItemEditing();
instance.focusItem(event, itemId);
}
};
const handleCancelItemLabelEditing = event => {
if (!hasPlugin(instance, useTreeViewLabel)) {
return;
}
if (selectorIsItemBeingEdited(store.value, itemId)) {
toggleItemEditing();
instance.focusItem(event, itemId);
}
};
const interactions = {
handleExpansion,
handleSelection,
handleCheckboxSelection,
toggleItemEditing,
handleSaveItemLabel,
handleCancelItemLabelEditing
};
return {
interactions,
status,
publicAPI
};
};