@mui/x-tree-view
Version:
The community edition of the MUI X Tree View components.
277 lines (262 loc) • 12.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TreeViewItemsPlugin = void 0;
var _formatErrorMessage2 = _interopRequireDefault(require("@mui/x-internals/formatErrorMessage"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _id = require("../id");
var _selectors = require("./selectors");
var _utils = require("./utils");
class TreeViewItemsPlugin {
// We can't type `store`, otherwise we get the following TS error:
// 'items' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
constructor(store) {
this.store = store;
}
/**
* Determines if the items state should be rebuilt based on the new and previous parameters.
*/
static shouldRebuildItemsState = (newParameters, previousParameters) => {
return ['items', 'isItemDisabled', 'isItemSelectionDisabled', 'getItemId', 'getItemLabel', 'getItemChildren'].some(key => {
const typedKey = key;
return newParameters[typedKey] !== previousParameters[typedKey];
});
};
/**
* Builds the state properties derived from the `items` prop.
*/
static buildItemsStateIfNeeded = parameters => {
return (0, _utils.buildItemsLookupsRecursively)({
storeParameters: parameters,
items: parameters.items,
parentId: null,
depth: 0,
isItemExpandable: (item, children) => !!children && children.length > 0
});
};
/**
* Get the item with the given id.
* When used in the Simple Tree View, it returns an object with the `id` and `label` properties.
* @param {TreeViewItemId} itemId The id of the item to retrieve.
* @returns {R} The item with the given id.
*/
getItem = itemId => _selectors.itemsSelectors.itemModel(this.store.state, itemId);
/**
* Get all the items in the same format as provided by `props.items`.
* @returns {R[]} The items in the tree.
*/
getItemTree = () => {
const getItemFromItemId = itemId => {
const item = _selectors.itemsSelectors.itemModel(this.store.state, itemId);
const itemToMutate = (0, _extends2.default)({}, item);
const newChildren = _selectors.itemsSelectors.itemOrderedChildrenIds(this.store.state, itemId);
if (newChildren.length > 0) {
itemToMutate.children = newChildren.map(getItemFromItemId);
} else {
delete itemToMutate.children;
}
return itemToMutate;
};
return _selectors.itemsSelectors.itemOrderedChildrenIds(this.store.state, null).map(getItemFromItemId);
};
/**
* Get the ids of a given item's children.
* Those ids are returned in the order they should be rendered.
* To get the root items, pass `null` as the `itemId`.
* @param {TreeViewItemId | null} itemId The id of the item to get the children of.
* @returns {TreeViewItemId[]} The ids of the item's children.
*/
getItemOrderedChildrenIds = itemId => _selectors.itemsSelectors.itemOrderedChildrenIds(this.store.state, itemId);
/** * Get the id of the parent item.
* @param {TreeViewItemId} itemId The id of the item to whose parentId we want to retrieve.
* @returns {TreeViewItemId | null} The id of the parent item.
*/
getParentId = itemId => {
const itemMeta = _selectors.itemsSelectors.itemMeta(this.store.state, itemId);
return itemMeta?.parentId || null;
};
/**
* Toggle the disabled state of the item with the given id.
* @param {object} parameters The params of the method.
* @param {TreeViewItemId } parameters.itemId The id of the item to get the children of.
* @param {boolean } parameters.shouldBeDisabled true if the item should be disabled.
*/
setIsItemDisabled = ({
itemId,
shouldBeDisabled
}) => {
if (!this.store.state.itemMetaLookup[itemId]) {
return;
}
const itemMetaLookup = (0, _extends2.default)({}, this.store.state.itemMetaLookup);
itemMetaLookup[itemId] = (0, _extends2.default)({}, itemMetaLookup[itemId], {
disabled: shouldBeDisabled ?? !itemMetaLookup[itemId].disabled
});
this.store.set('itemMetaLookup', itemMetaLookup);
};
/**
* Add items to the tree.
* The items are added as children of the item with the given `parentId`, or at the root level if `parentId` is `null` or not defined.
* @param {AddItemsParameters<R>} parameters The items to add and their position in the tree.
*/
addItems = ({
items,
parentId = null,
index
}) => {
if (items.length === 0) {
return;
}
if (parentId != null && _selectors.itemsSelectors.itemMeta(this.store.state, parentId) == null) {
throw new Error(process.env.NODE_ENV !== "production" ? `MUI X Tree View: Unable to add items to the parent with id "${parentId}" because it is not present in the tree. ` + 'Pass the id of an existing item, or `null` to add the items at the root level.' : (0, _formatErrorMessage2.default)(295, parentId));
}
const parentDepth = parentId == null ? -1 : _selectors.itemsSelectors.itemDepth(this.store.state, parentId);
// When the items are lazy loaded, an item can be expandable even if its children are not loaded yet.
const dataSource = this.store.parameters.dataSource;
const isItemExpandable = (item, children) => {
if (children != null && children.length > 0) {
return true;
}
return dataSource == null ? false : dataSource.getChildrenCount(item) !== 0;
};
const {
itemMetaLookup: metaLookup,
itemModelLookup: modelLookup,
itemOrderedChildrenIdsLookup: orderedChildrenIdsLookup,
itemChildrenIndexesLookup: childrenIndexesLookup
} = (0, _utils.buildItemsLookupsRecursively)({
storeParameters: this.store.parameters,
items,
parentId,
depth: parentDepth + 1,
isItemExpandable,
existingItemMetaLookup: this.store.state.itemMetaLookup
});
// `buildItemsLookups` allows an id to be re-used by an item with the same parent,
// which is only valid when rebuilding the state from the `items` prop.
for (const id of Object.keys(metaLookup)) {
if (this.store.state.itemMetaLookup[id] != null) {
throw new Error(process.env.NODE_ENV !== "production" ? `MUI X Tree View: All items must have a unique \`id\` property. ` + `The id "${id}" is used by multiple items. ` + 'Use the `getItemId` prop to specify a custom id for each item if needed.' : (0, _formatErrorMessage2.default)(194, id));
}
}
const parentIdWithDefault = parentId ?? _utils.TREE_VIEW_ROOT_PARENT_ID;
const existingChildrenIds = _selectors.itemsSelectors.itemOrderedChildrenIds(this.store.state, parentId);
const insertionIndex = index ?? existingChildrenIds.length;
if (insertionIndex < 0 || insertionIndex > existingChildrenIds.length) {
throw new Error(process.env.NODE_ENV !== "production" ? `MUI X Tree View: Unable to add items at index "${insertionIndex}" because it is out of range. ` + `The index must be between 0 and the amount of children of the parent item (${existingChildrenIds.length}).` : (0, _formatErrorMessage2.default)(296, insertionIndex, existingChildrenIds.length));
}
const newChildrenIds = orderedChildrenIdsLookup[parentIdWithDefault];
const mergedChildrenIds = [...existingChildrenIds];
mergedChildrenIds.splice(insertionIndex, 0, ...newChildrenIds);
orderedChildrenIdsLookup[parentIdWithDefault] = mergedChildrenIds;
childrenIndexesLookup[parentIdWithDefault] = (0, _utils.buildSiblingIndexes)(mergedChildrenIds);
const newMetaLookup = (0, _extends2.default)({}, this.store.state.itemMetaLookup, metaLookup);
if (parentId != null && !newMetaLookup[parentId].expandable) {
newMetaLookup[parentId] = (0, _extends2.default)({}, newMetaLookup[parentId], {
expandable: true
});
}
this.store.update({
itemMetaLookup: newMetaLookup,
itemModelLookup: (0, _extends2.default)({}, this.store.state.itemModelLookup, modelLookup),
itemOrderedChildrenIdsLookup: (0, _extends2.default)({}, this.store.state.itemOrderedChildrenIdsLookup, orderedChildrenIdsLookup),
itemChildrenIndexesLookup: (0, _extends2.default)({}, this.store.state.itemChildrenIndexesLookup, childrenIndexesLookup)
});
this.store.selection.propagateSelectionToNewItems(parentId, newChildrenIds);
};
buildPublicAPI = () => {
return {
getItem: this.getItem,
getItemDOMElement: this.getItemDOMElement,
getItemOrderedChildrenIds: this.getItemOrderedChildrenIds,
getItemTree: this.getItemTree,
getParentId: this.getParentId,
setIsItemDisabled: this.setIsItemDisabled
};
};
/**
* Get the DOM element of the item with the given id.
* @param {TreeViewItemId} itemId The id of the item to get the DOM element of.
* @returns {HTMLElement | null} The DOM element of the item with the given id.
*/
getItemDOMElement = itemId => {
const itemMeta = _selectors.itemsSelectors.itemMeta(this.store.state, itemId);
if (itemMeta == null) {
return null;
}
const idAttribute = _id.idSelectors.treeItemIdAttribute(this.store.state, itemId, itemMeta.idAttribute);
return document.getElementById(idAttribute);
};
/**
* Add an array of items to the tree.
* @param {SetItemChildrenParameters<R>} args The items to add to the tree and information about their ancestors.
*/
setItemChildren = ({
items,
parentId,
getChildrenCount
}) => {
const parentIdWithDefault = parentId ?? _utils.TREE_VIEW_ROOT_PARENT_ID;
const parentDepth = parentId == null ? -1 : _selectors.itemsSelectors.itemDepth(this.store.state, parentId);
const {
metaLookup,
modelLookup,
orderedChildrenIds,
childrenIndexes
} = (0, _utils.buildItemsLookups)({
storeParameters: this.store.parameters,
items,
parentId,
depth: parentDepth + 1,
isItemExpandable: getChildrenCount ? item => getChildrenCount(item) !== 0 : () => false,
otherItemsMetaLookup: _selectors.itemsSelectors.itemMetaLookup(this.store.state)
});
this.store.update({
itemModelLookup: (0, _extends2.default)({}, this.store.state.itemModelLookup, modelLookup),
itemMetaLookup: (0, _extends2.default)({}, this.store.state.itemMetaLookup, metaLookup),
itemOrderedChildrenIdsLookup: (0, _extends2.default)({}, this.store.state.itemOrderedChildrenIdsLookup, {
[parentIdWithDefault]: orderedChildrenIds
}),
itemChildrenIndexesLookup: (0, _extends2.default)({}, this.store.state.itemChildrenIndexesLookup, {
[parentIdWithDefault]: childrenIndexes
})
});
};
/**
* Remove the children of an item.
* @param {TreeViewItemId | null} parentId The id of the item to remove the children of.
*/
removeChildren = parentId => {
const itemMetaLookup = this.store.state.itemMetaLookup;
const newMetaMap = Object.keys(itemMetaLookup).reduce((acc, key) => {
const item = itemMetaLookup[key];
if (item.parentId === parentId) {
return acc;
}
return (0, _extends2.default)({}, acc, {
[item.id]: item
});
}, {});
const newItemOrderedChildrenIdsLookup = (0, _extends2.default)({}, this.store.state.itemOrderedChildrenIdsLookup);
const newItemChildrenIndexesLookup = (0, _extends2.default)({}, this.store.state.itemChildrenIndexesLookup);
const cleanId = parentId ?? _utils.TREE_VIEW_ROOT_PARENT_ID;
delete newItemChildrenIndexesLookup[cleanId];
delete newItemOrderedChildrenIdsLookup[cleanId];
this.store.update({
itemMetaLookup: newMetaMap,
itemOrderedChildrenIdsLookup: newItemOrderedChildrenIdsLookup,
itemChildrenIndexesLookup: newItemChildrenIndexesLookup
});
};
/**
* Callback fired when the `content` slot of a given Tree Item is clicked.
* @param {React.MouseEvent} event The DOM event that triggered the change.
* @param {TreeViewItemId} itemId The id of the item being clicked.
*/
handleItemClick = (event, itemId) => {
this.store.parameters.onItemClick?.(event, itemId);
};
}
exports.TreeViewItemsPlugin = TreeViewItemsPlugin;