@mui/x-tree-view
Version:
The community edition of the MUI X Tree View components.
234 lines (232 loc) • 9.47 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useAssertModelConsistency } from '@mui/x-internals/useAssertModelConsistency';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { findOrderInTremauxTree, getAllNavigableItems, getFirstNavigableItem, getLastNavigableItem, getNonDisabledItemsInRange } from "../../utils/tree.js";
import { propagateSelection, getAddedAndRemovedItems, getLookupFromArray } from "./useTreeViewSelection.utils.js";
import { selectorIsItemSelected, selectorIsMultiSelectEnabled, selectorIsSelectionEnabled, selectorSelectionModel, selectorSelectionModelArray } from "./useTreeViewSelection.selectors.js";
import { useTreeViewSelectionItemPlugin } from "./useTreeViewSelection.itemPlugin.js";
export const useTreeViewSelection = ({
store,
params
}) => {
useAssertModelConsistency({
componentName: 'Tree View',
propName: 'selectedItems',
controlled: params.selectedItems,
defaultValue: params.defaultSelectedItems
});
const lastSelectedItem = React.useRef(null);
const lastSelectedRange = React.useRef({});
const setSelectedItems = (event, newModel, additionalItemsToPropagate) => {
const oldModel = selectorSelectionModel(store.value);
let cleanModel;
const isMultiSelectEnabled = selectorIsMultiSelectEnabled(store.value);
if (isMultiSelectEnabled && (params.selectionPropagation.descendants || params.selectionPropagation.parents)) {
cleanModel = propagateSelection({
store,
selectionPropagation: params.selectionPropagation,
newModel: newModel,
oldModel: oldModel,
additionalItemsToPropagate
});
} else {
cleanModel = newModel;
}
if (params.onItemSelectionToggle) {
if (isMultiSelectEnabled) {
const changes = getAddedAndRemovedItems({
store,
newModel: cleanModel,
oldModel: oldModel
});
if (params.onItemSelectionToggle) {
changes.added.forEach(itemId => {
params.onItemSelectionToggle(event, itemId, true);
});
changes.removed.forEach(itemId => {
params.onItemSelectionToggle(event, itemId, false);
});
}
} else if (params.onItemSelectionToggle && cleanModel !== oldModel) {
if (oldModel != null) {
params.onItemSelectionToggle(event, oldModel, false);
}
if (cleanModel != null) {
params.onItemSelectionToggle(event, cleanModel, true);
}
}
}
if (params.selectedItems === undefined) {
store.update(prevState => _extends({}, prevState, {
selection: _extends({}, prevState.selection, {
selectedItems: cleanModel
})
}));
}
params.onSelectedItemsChange?.(event, cleanModel);
};
const setItemSelection = ({
itemId,
event = null,
keepExistingSelection = false,
shouldBeSelected
}) => {
if (!selectorIsSelectionEnabled(store.value)) {
return;
}
let newSelected;
const isMultiSelectEnabled = selectorIsMultiSelectEnabled(store.value);
if (keepExistingSelection) {
const oldSelected = selectorSelectionModelArray(store.value);
const isSelectedBefore = selectorIsItemSelected(store.value, itemId);
if (isSelectedBefore && (shouldBeSelected === false || shouldBeSelected == null)) {
newSelected = oldSelected.filter(id => id !== itemId);
} else if (!isSelectedBefore && (shouldBeSelected === true || shouldBeSelected == null)) {
newSelected = [itemId].concat(oldSelected);
} else {
newSelected = oldSelected;
}
} else {
// eslint-disable-next-line no-lonely-if
if (shouldBeSelected === false || shouldBeSelected == null && selectorIsItemSelected(store.value, itemId)) {
newSelected = isMultiSelectEnabled ? [] : null;
} else {
newSelected = isMultiSelectEnabled ? [itemId] : itemId;
}
}
setSelectedItems(event, newSelected,
// If shouldBeSelected === selectorIsItemSelected(store, itemId), we still want to propagate the select.
// This is useful when the element is in an indeterminate state.
[itemId]);
lastSelectedItem.current = itemId;
lastSelectedRange.current = {};
};
const selectRange = (event, [start, end]) => {
const isMultiSelectEnabled = selectorIsMultiSelectEnabled(store.value);
if (!isMultiSelectEnabled) {
return;
}
let newSelectedItems = selectorSelectionModelArray(store.value).slice();
// If the last selection was a range selection,
// remove the items that were part of the last range from the model
if (Object.keys(lastSelectedRange.current).length > 0) {
newSelectedItems = newSelectedItems.filter(id => !lastSelectedRange.current[id]);
}
// Add to the model the items that are part of the new range and not already part of the model.
const selectedItemsLookup = getLookupFromArray(newSelectedItems);
const range = getNonDisabledItemsInRange(store.value, start, end);
const itemsToAddToModel = range.filter(id => !selectedItemsLookup[id]);
newSelectedItems = newSelectedItems.concat(itemsToAddToModel);
setSelectedItems(event, newSelectedItems);
lastSelectedRange.current = getLookupFromArray(range);
};
const expandSelectionRange = (event, itemId) => {
if (lastSelectedItem.current != null) {
const [start, end] = findOrderInTremauxTree(store.value, itemId, lastSelectedItem.current);
selectRange(event, [start, end]);
}
};
const selectRangeFromStartToItem = (event, itemId) => {
selectRange(event, [getFirstNavigableItem(store.value), itemId]);
};
const selectRangeFromItemToEnd = (event, itemId) => {
selectRange(event, [itemId, getLastNavigableItem(store.value)]);
};
const selectAllNavigableItems = event => {
const isMultiSelectEnabled = selectorIsMultiSelectEnabled(store.value);
if (!isMultiSelectEnabled) {
return;
}
const navigableItems = getAllNavigableItems(store.value);
setSelectedItems(event, navigableItems);
lastSelectedRange.current = getLookupFromArray(navigableItems);
};
const selectItemFromArrowNavigation = (event, currentItem, nextItem) => {
const isMultiSelectEnabled = selectorIsMultiSelectEnabled(store.value);
if (!isMultiSelectEnabled) {
return;
}
let newSelectedItems = selectorSelectionModelArray(store.value).slice();
if (Object.keys(lastSelectedRange.current).length === 0) {
newSelectedItems.push(nextItem);
lastSelectedRange.current = {
[currentItem]: true,
[nextItem]: true
};
} else {
if (!lastSelectedRange.current[currentItem]) {
lastSelectedRange.current = {};
}
if (lastSelectedRange.current[nextItem]) {
newSelectedItems = newSelectedItems.filter(id => id !== currentItem);
delete lastSelectedRange.current[currentItem];
} else {
newSelectedItems.push(nextItem);
lastSelectedRange.current[nextItem] = true;
}
}
setSelectedItems(event, newSelectedItems);
};
useEnhancedEffect(() => {
store.update(prevState => _extends({}, prevState, {
selection: {
selectedItems: params.selectedItems === undefined ? prevState.selection.selectedItems : params.selectedItems,
isEnabled: !params.disableSelection,
isMultiSelectEnabled: params.multiSelect,
isCheckboxSelectionEnabled: params.checkboxSelection,
selectionPropagation: {
descendants: params.selectionPropagation.descendants,
parents: params.selectionPropagation.parents
}
}
}));
}, [store, params.selectedItems, params.multiSelect, params.checkboxSelection, params.disableSelection, params.selectionPropagation.descendants, params.selectionPropagation.parents]);
return {
getRootProps: () => ({
'aria-multiselectable': params.multiSelect
}),
publicAPI: {
setItemSelection
},
instance: {
setItemSelection,
selectAllNavigableItems,
expandSelectionRange,
selectRangeFromStartToItem,
selectRangeFromItemToEnd,
selectItemFromArrowNavigation
}
};
};
useTreeViewSelection.itemPlugin = useTreeViewSelectionItemPlugin;
const DEFAULT_SELECTED_ITEMS = [];
const EMPTY_SELECTION_PROPAGATION = {};
useTreeViewSelection.applyDefaultValuesToParams = ({
params
}) => _extends({}, params, {
disableSelection: params.disableSelection ?? false,
multiSelect: params.multiSelect ?? false,
checkboxSelection: params.checkboxSelection ?? false,
defaultSelectedItems: params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_ITEMS : null),
selectionPropagation: params.selectionPropagation ?? EMPTY_SELECTION_PROPAGATION
});
useTreeViewSelection.getInitialState = params => ({
selection: {
selectedItems: params.selectedItems === undefined ? params.defaultSelectedItems : params.selectedItems,
isEnabled: !params.disableSelection,
isMultiSelectEnabled: params.multiSelect,
isCheckboxSelectionEnabled: params.checkboxSelection,
selectionPropagation: params.selectionPropagation
}
});
useTreeViewSelection.params = {
disableSelection: true,
multiSelect: true,
checkboxSelection: true,
defaultSelectedItems: true,
selectedItems: true,
onSelectedItemsChange: true,
onItemSelectionToggle: true,
selectionPropagation: true
};