UNPKG

@mui/x-tree-view

Version:

The community edition of the MUI X Tree View components.

217 lines (207 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isTargetInDescendants = exports.getPreviousNavigableItem = exports.getNonDisabledItemsInRange = exports.getNextNavigableItem = exports.getLastNavigableItem = exports.getFirstNavigableItem = exports.getAllNavigableItems = exports.findOrderInTremauxTree = void 0; var _useTreeViewExpansion = require("../plugins/useTreeViewExpansion/useTreeViewExpansion.selectors"); var _useTreeViewItems = require("../plugins/useTreeViewItems/useTreeViewItems.selectors"); const getLastNavigableItemInArray = (state, items) => { // Equivalent to Array.prototype.findLastIndex let itemIndex = items.length - 1; while (itemIndex >= 0 && !_useTreeViewItems.itemsSelectors.canItemBeFocused(state, items[itemIndex])) { itemIndex -= 1; } if (itemIndex === -1) { return undefined; } return items[itemIndex]; }; const getPreviousNavigableItem = (state, itemId) => { const itemMeta = _useTreeViewItems.itemsSelectors.itemMeta(state, itemId); if (!itemMeta) { return null; } const siblings = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemMeta.parentId); const itemIndex = _useTreeViewItems.itemsSelectors.itemIndex(state, itemId); // TODO: What should we do if the parent is not navigable? if (itemIndex === 0) { return itemMeta.parentId; } // Finds the previous navigable sibling. let previousNavigableSiblingIndex = itemIndex - 1; while (!_useTreeViewItems.itemsSelectors.canItemBeFocused(state, siblings[previousNavigableSiblingIndex]) && previousNavigableSiblingIndex >= 0) { previousNavigableSiblingIndex -= 1; } if (previousNavigableSiblingIndex === -1) { // If we are at depth 0, then it means all the items above the current item are not navigable. if (itemMeta.parentId == null) { return null; } // Otherwise, we can try to go up a level and find the previous navigable item. return getPreviousNavigableItem(state, itemMeta.parentId); } // Finds the last navigable ancestor of the previous navigable sibling. let currentItemId = siblings[previousNavigableSiblingIndex]; let lastNavigableChild = getLastNavigableItemInArray(state, _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, currentItemId)); while (_useTreeViewExpansion.expansionSelectors.isItemExpanded(state, currentItemId) && lastNavigableChild != null) { currentItemId = lastNavigableChild; lastNavigableChild = getLastNavigableItemInArray(state, _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, currentItemId)); } return currentItemId; }; exports.getPreviousNavigableItem = getPreviousNavigableItem; const getNextNavigableItem = (state, itemId) => { // If the item is expanded and has some navigable children, return the first of them. if (_useTreeViewExpansion.expansionSelectors.isItemExpanded(state, itemId)) { const firstNavigableChild = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemId).find(childId => _useTreeViewItems.itemsSelectors.canItemBeFocused(state, childId)); if (firstNavigableChild != null) { return firstNavigableChild; } } let itemMeta = _useTreeViewItems.itemsSelectors.itemMeta(state, itemId); while (itemMeta != null) { // Try to find the first navigable sibling after the current item. const siblings = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemMeta.parentId); const currentItemIndex = _useTreeViewItems.itemsSelectors.itemIndex(state, itemMeta.id); if (currentItemIndex < siblings.length - 1) { let nextItemIndex = currentItemIndex + 1; while (!_useTreeViewItems.itemsSelectors.canItemBeFocused(state, siblings[nextItemIndex]) && nextItemIndex < siblings.length - 1) { nextItemIndex += 1; } if (_useTreeViewItems.itemsSelectors.canItemBeFocused(state, siblings[nextItemIndex])) { return siblings[nextItemIndex]; } } // If the sibling does not exist, go up a level to the parent and try again. itemMeta = _useTreeViewItems.itemsSelectors.itemMeta(state, itemMeta.parentId); } return null; }; exports.getNextNavigableItem = getNextNavigableItem; const getLastNavigableItem = state => { let itemId = null; while (itemId == null || _useTreeViewExpansion.expansionSelectors.isItemExpanded(state, itemId)) { const children = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemId); const lastNavigableChild = getLastNavigableItemInArray(state, children); // The item has no navigable children. if (lastNavigableChild == null) { return itemId; } itemId = lastNavigableChild; } return itemId; }; exports.getLastNavigableItem = getLastNavigableItem; const getFirstNavigableItem = state => _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, null).find(itemId => _useTreeViewItems.itemsSelectors.canItemBeFocused(state, itemId)); /** * This is used to determine the start and end of a selection range so * we can get the items between the two border items. * * It finds the items' common ancestor using * a naive implementation of a lowest common ancestor algorithm * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). * Then compares the ancestor's 2 children that are ancestors of itemA and ItemB * so we can compare their indexes to work out which item comes first in a depth first search. * (https://en.wikipedia.org/wiki/Depth-first_search) * * Another way to put it is which item is shallower in a trémaux tree * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree */ exports.getFirstNavigableItem = getFirstNavigableItem; const findOrderInTremauxTree = (state, itemAId, itemBId) => { if (itemAId === itemBId) { return [itemAId, itemBId]; } const itemMetaA = _useTreeViewItems.itemsSelectors.itemMeta(state, itemAId); const itemMetaB = _useTreeViewItems.itemsSelectors.itemMeta(state, itemBId); if (!itemMetaA || !itemMetaB) { return [itemAId, itemBId]; } if (itemMetaA.parentId === itemMetaB.id || itemMetaB.parentId === itemMetaA.id) { return itemMetaB.parentId === itemMetaA.id ? [itemMetaA.id, itemMetaB.id] : [itemMetaB.id, itemMetaA.id]; } const aFamily = [itemMetaA.id]; const bFamily = [itemMetaB.id]; let aAncestor = itemMetaA.parentId; let bAncestor = itemMetaB.parentId; let aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; let bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; let continueA = true; let continueB = true; while (!bAncestorIsCommon && !aAncestorIsCommon) { if (continueA) { aFamily.push(aAncestor); aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; continueA = aAncestor !== null; if (!aAncestorIsCommon && continueA) { aAncestor = _useTreeViewItems.itemsSelectors.itemParentId(state, aAncestor); } } if (continueB && !aAncestorIsCommon) { bFamily.push(bAncestor); bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; continueB = bAncestor !== null; if (!bAncestorIsCommon && continueB) { bAncestor = _useTreeViewItems.itemsSelectors.itemParentId(state, bAncestor); } } } const commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor; const ancestorFamily = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, commonAncestor); const aSide = aFamily[aFamily.indexOf(commonAncestor) - 1]; const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1]; return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [itemAId, itemBId] : [itemBId, itemAId]; }; exports.findOrderInTremauxTree = findOrderInTremauxTree; const getNonDisabledItemsInRange = (state, itemAId, itemBId) => { const getNextItem = itemId => { // If the item is expanded and has some children, return the first of them. if (_useTreeViewExpansion.expansionSelectors.isItemExpandable(state, itemId) && _useTreeViewExpansion.expansionSelectors.isItemExpanded(state, itemId)) { return _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemId)[0]; } let itemMeta = _useTreeViewItems.itemsSelectors.itemMeta(state, itemId); while (itemMeta != null) { // Try to find the first navigable sibling after the current item. const siblings = _useTreeViewItems.itemsSelectors.itemOrderedChildrenIds(state, itemMeta.parentId); const currentItemIndex = _useTreeViewItems.itemsSelectors.itemIndex(state, itemMeta.id); if (currentItemIndex < siblings.length - 1) { return siblings[currentItemIndex + 1]; } // If the item is the last of its siblings, go up a level to the parent and try again. itemMeta = itemMeta.parentId ? _useTreeViewItems.itemsSelectors.itemMeta(state, itemMeta.parentId) : null; } throw new Error('Invalid range'); }; const [first, last] = findOrderInTremauxTree(state, itemAId, itemBId); const items = [first]; let current = first; while (current !== last) { current = getNextItem(current); if (!_useTreeViewItems.itemsSelectors.isItemDisabled(state, current)) { items.push(current); } } return items; }; exports.getNonDisabledItemsInRange = getNonDisabledItemsInRange; const getAllNavigableItems = state => { let item = getFirstNavigableItem(state); const navigableItems = []; while (item != null) { navigableItems.push(item); item = getNextNavigableItem(state, item); } return navigableItems; }; /** * Checks if the target is in a descendant of this item. * This can prevent from firing some logic on the ancestors on the interacted item when the event handler is on the root. * @param {HTMLElement} target The target to check * @param {HTMLElement | null} itemRoot The root of the item to check if the event target is in its descendants * @returns {boolean} Whether the target is in a descendant of this item */ exports.getAllNavigableItems = getAllNavigableItems; const isTargetInDescendants = (target, itemRoot) => { return itemRoot !== target.closest('*[role="treeitem"]'); }; exports.isTargetInDescendants = isTargetInDescendants;