UNPKG

@mui/x-tree-view

Version:

The community edition of the MUI X Tree View components.

334 lines (331 loc) 12.4 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import { publishTreeViewEvent } from "../../utils/publishTreeViewEvent.js"; import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from "./useTreeViewItems.utils.js"; import { TreeViewItemDepthContext } from "../../TreeViewItemDepthContext/index.js"; import { selectorItemMeta, selectorItemOrderedChildrenIds, selectorItemModel, selectorItemDepth } from "./useTreeViewItems.selectors.js"; import { selectorTreeViewId } from "../../corePlugins/useTreeViewId/useTreeViewId.selectors.js"; import { generateTreeItemIdAttribute } from "../../corePlugins/useTreeViewId/useTreeViewId.utils.js"; import { jsx as _jsx } from "react/jsx-runtime"; const checkId = (id, item, itemMetaLookup) => { if (id == null) { throw new Error(['MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', 'An item was provided without id in the `items` prop:', JSON.stringify(item)].join('\n')); } if (itemMetaLookup[id] != null) { throw new Error(['MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', `Two items were provided with the same id in the \`items\` prop: "${id}"`].join('\n')); } }; const processItemsLookups = ({ disabledItemsFocusable, items, isItemDisabled, getItemLabel, getItemChildren, getItemId, initialDepth = 0, initialParentId = null, getChildrenCount, ignoreChildren = false }) => { const itemMetaLookup = {}; const itemModelLookup = {}; const itemOrderedChildrenIdsLookup = { [TREE_VIEW_ROOT_PARENT_ID]: [] }; const processItem = (item, depth, parentId) => { const id = getItemId ? getItemId(item) : item.id; checkId(id, item, itemMetaLookup); const label = getItemLabel ? getItemLabel(item) : item.label; if (label == null) { throw new Error(['MUI X: The Tree View component requires all items to have a `label` property.', 'Alternatively, you can use the `getItemLabel` prop to specify a custom label for each item.', 'An item was provided without label in the `items` prop:', JSON.stringify(item)].join('\n')); } const children = getItemChildren ? getItemChildren(item) : item.children; itemMetaLookup[id] = { id, label, parentId, idAttribute: undefined, expandable: getChildrenCount ? getChildrenCount(item) > 0 : !!children?.length, disabled: isItemDisabled ? isItemDisabled(item) : false, depth }; itemModelLookup[id] = item; const parentIdWithDefault = parentId ?? TREE_VIEW_ROOT_PARENT_ID; if (!itemOrderedChildrenIdsLookup[parentIdWithDefault]) { itemOrderedChildrenIdsLookup[parentIdWithDefault] = []; } itemOrderedChildrenIdsLookup[parentIdWithDefault].push(id); // if lazy loading is enabled, we don't want to process children passed through the `items` prop if (!ignoreChildren) { children?.forEach(child => processItem(child, depth + 1, id)); } }; items?.forEach(item => processItem(item, initialDepth, initialParentId)); const itemChildrenIndexesLookup = {}; Object.keys(itemOrderedChildrenIdsLookup).forEach(parentId => { itemChildrenIndexesLookup[parentId] = buildSiblingIndexes(itemOrderedChildrenIdsLookup[parentId]); }); return { disabledItemsFocusable, itemMetaLookup, itemModelLookup, itemOrderedChildrenIdsLookup, itemChildrenIndexesLookup }; }; export const useTreeViewItems = ({ instance, params, store }) => { const getItem = React.useCallback(itemId => selectorItemModel(store.value, itemId), [store]); const getParentId = React.useCallback(itemId => { const itemMeta = selectorItemMeta(store.value, itemId); return itemMeta?.parentId || null; }, [store]); const setTreeViewLoading = useEventCallback(isLoading => { store.update(prevState => _extends({}, prevState, { items: _extends({}, prevState.items, { loading: isLoading }) })); }); const setTreeViewError = useEventCallback(error => { store.update(prevState => _extends({}, prevState, { items: _extends({}, prevState.items, { error }) })); }); const setIsItemDisabled = useEventCallback(({ itemId, shouldBeDisabled }) => { store.update(prevState => { if (!prevState.items.itemMetaLookup[itemId]) { return prevState; } const itemMetaLookup = _extends({}, prevState.items.itemMetaLookup); itemMetaLookup[itemId] = _extends({}, itemMetaLookup[itemId], { disabled: shouldBeDisabled ?? !itemMetaLookup[itemId].disabled }); return _extends({}, prevState, { items: _extends({}, prevState.items, { itemMetaLookup }) }); }); }); const getItemTree = React.useCallback(() => { const getItemFromItemId = itemId => { const item = selectorItemModel(store.value, itemId); const newChildren = selectorItemOrderedChildrenIds(store.value, itemId); if (newChildren.length > 0) { item.children = newChildren.map(getItemFromItemId); } else { delete item.children; } return item; }; return selectorItemOrderedChildrenIds(store.value, null).map(getItemFromItemId); }, [store]); const getItemOrderedChildrenIds = React.useCallback(itemId => selectorItemOrderedChildrenIds(store.value, itemId), [store]); const getItemDOMElement = itemId => { const itemMeta = selectorItemMeta(store.value, itemId); if (itemMeta == null) { return null; } const idAttribute = generateTreeItemIdAttribute({ treeId: selectorTreeViewId(store.value), itemId, id: itemMeta.idAttribute }); return document.getElementById(idAttribute); }; const areItemUpdatesPreventedRef = React.useRef(false); const preventItemUpdates = React.useCallback(() => { areItemUpdatesPreventedRef.current = true; }, []); const areItemUpdatesPrevented = React.useCallback(() => areItemUpdatesPreventedRef.current, []); const addItems = ({ items, parentId, depth, getChildrenCount }) => { if (items) { const newState = processItemsLookups({ disabledItemsFocusable: params.disabledItemsFocusable, items, isItemDisabled: params.isItemDisabled, getItemId: params.getItemId, getItemLabel: params.getItemLabel, getItemChildren: params.getItemChildren, getChildrenCount, initialDepth: depth, initialParentId: parentId, ignoreChildren: true }); store.update(prevState => { let newItems; if (parentId) { newItems = { itemModelLookup: _extends({}, prevState.items.itemModelLookup, newState.itemModelLookup), itemMetaLookup: _extends({}, prevState.items.itemMetaLookup, newState.itemMetaLookup), itemOrderedChildrenIdsLookup: _extends({}, newState.itemOrderedChildrenIdsLookup, prevState.items.itemOrderedChildrenIdsLookup), itemChildrenIndexesLookup: _extends({}, newState.itemChildrenIndexesLookup, prevState.items.itemChildrenIndexesLookup) }; } else { newItems = { itemModelLookup: newState.itemModelLookup, itemMetaLookup: newState.itemMetaLookup, itemOrderedChildrenIdsLookup: newState.itemOrderedChildrenIdsLookup, itemChildrenIndexesLookup: newState.itemChildrenIndexesLookup }; } Object.values(prevState.items.itemMetaLookup).forEach(item => { if (!newItems.itemMetaLookup[item.id]) { publishTreeViewEvent(instance, 'removeItem', { id: item.id }); } }); return _extends({}, prevState, { items: _extends({}, prevState.items, newItems) }); }); } }; const removeChildren = parentId => { store.update(prevState => { if (!parentId) { return _extends({}, prevState, { items: _extends({}, prevState.items, { itemMetaLookup: {}, itemOrderedChildrenIdsLookup: {}, itemChildrenIndexesLookup: {} }) }); } const newMetaMap = Object.keys(prevState.items.itemMetaLookup).reduce((acc, key) => { const item = prevState.items.itemMetaLookup[key]; if (item.parentId === parentId) { publishTreeViewEvent(instance, 'removeItem', { id: item.id }); return acc; } return _extends({}, acc, { [item.id]: item }); }, {}); const newItemOrderedChildrenIdsLookup = prevState.items.itemOrderedChildrenIdsLookup; const newItemChildrenIndexesLookup = prevState.items.itemChildrenIndexesLookup; delete newItemChildrenIndexesLookup[parentId]; delete newItemOrderedChildrenIdsLookup[parentId]; return _extends({}, prevState, { items: _extends({}, prevState.items, { itemMetaLookup: newMetaMap, itemOrderedChildrenIdsLookup: newItemOrderedChildrenIdsLookup, itemChildrenIndexesLookup: newItemChildrenIndexesLookup }) }); }); }; React.useEffect(() => { if (instance.areItemUpdatesPrevented()) { return; } store.update(prevState => { const newState = processItemsLookups({ disabledItemsFocusable: params.disabledItemsFocusable, items: params.items, isItemDisabled: params.isItemDisabled, getItemId: params.getItemId, getItemLabel: params.getItemLabel, getItemChildren: params.getItemChildren }); Object.values(prevState.items.itemMetaLookup).forEach(item => { if (!newState.itemMetaLookup[item.id]) { publishTreeViewEvent(instance, 'removeItem', { id: item.id }); } }); return _extends({}, prevState, { items: _extends({}, prevState.items, newState) }); }); }, [instance, store, params.items, params.disabledItemsFocusable, params.isItemDisabled, params.getItemId, params.getItemLabel, params.getItemChildren]); // Wrap `props.onItemClick` with `useEventCallback` to prevent unneeded context updates. const handleItemClick = useEventCallback((event, itemId) => { if (params.onItemClick) { params.onItemClick(event, itemId); } }); return { getRootProps: () => ({ style: { '--TreeView-itemChildrenIndentation': typeof params.itemChildrenIndentation === 'number' ? `${params.itemChildrenIndentation}px` : params.itemChildrenIndentation } }), publicAPI: { getItem, getItemDOMElement, getItemTree, getItemOrderedChildrenIds, setIsItemDisabled, getParentId }, instance: { getItemDOMElement, preventItemUpdates, areItemUpdatesPrevented, addItems, setTreeViewLoading, setTreeViewError, removeChildren, handleItemClick } }; }; useTreeViewItems.getInitialState = params => ({ items: _extends({}, processItemsLookups({ disabledItemsFocusable: params.disabledItemsFocusable, items: params.items, isItemDisabled: params.isItemDisabled, getItemId: params.getItemId, getItemLabel: params.getItemLabel, getItemChildren: params.getItemChildren }), { loading: false, error: null }) }); useTreeViewItems.applyDefaultValuesToParams = ({ params }) => _extends({}, params, { disabledItemsFocusable: params.disabledItemsFocusable ?? false, itemChildrenIndentation: params.itemChildrenIndentation ?? '12px' }); useTreeViewItems.wrapRoot = ({ children }) => { return /*#__PURE__*/_jsx(TreeViewItemDepthContext.Provider, { value: selectorItemDepth, children: children }); }; if (process.env.NODE_ENV !== "production") useTreeViewItems.wrapRoot.displayName = "useTreeViewItems.wrapRoot"; useTreeViewItems.params = { disabledItemsFocusable: true, items: true, isItemDisabled: true, getItemLabel: true, getItemChildren: true, getItemId: true, onItemClick: true, itemChildrenIndentation: true };