UNPKG

@mui/x-tree-view

Version:

The community edition of the MUI X Tree View components.

163 lines (157 loc) 5.9 kB
'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 }; };