UNPKG

@mui/x-tree-view

Version:

The community edition of the MUI X Tree View components.

107 lines (101 loc) 3.86 kB
import { expansionSelectors } from "../expansion/index.js"; import { focusSelectors } from "./selectors.js"; import { itemsSelectors } from "../items/index.js"; export class TreeViewFocusPlugin { // We can't type `store`, otherwise we get the following TS error: // 'focus' 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; // Whenever the items change, we need to ensure the focused item is still present. this.store.registerStoreEffect(itemsSelectors.itemMetaLookup, () => { const focusedItemId = focusSelectors.focusedItemId(store.state); if (focusedItemId == null) { return; } const hasItemBeenRemoved = !itemsSelectors.itemMeta(store.state, focusedItemId); if (!hasItemBeenRemoved) { return; } const defaultFocusableItemId = focusSelectors.defaultFocusableItemId(store.state); if (defaultFocusableItemId == null) { this.setFocusedItemId(null); return; } this.applyItemFocus(null, defaultFocusableItemId); }); } setFocusedItemId = itemId => { const focusedItemId = focusSelectors.focusedItemId(this.store.state); if (focusedItemId === itemId) { return; } this.store.set('focusedItemId', itemId); }; applyItemFocus = (event, itemId) => { this.store.items.getItemDOMElement(itemId)?.focus(); this.setFocusedItemId(itemId); this.store.parameters.onItemFocus?.(event, itemId); }; buildPublicAPI = () => { return { focusItem: this.focusItem }; }; /** * Focus the item with the given id. * * If the item is the child of a collapsed item, then this method will do nothing. * Make sure to expand the ancestors of the item before calling this method if needed. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. * @param {TreeViewItemId} itemId The id of the item to focus. */ focusItem = (event, itemId) => { // If we receive an itemId, and it is visible, the focus will be set to it const itemMeta = itemsSelectors.itemMeta(this.store.state, itemId); const isItemVisible = itemMeta && (itemMeta.parentId == null || expansionSelectors.isItemExpanded(this.store.state, itemMeta.parentId)); if (isItemVisible) { this.applyItemFocus(event, itemId); } }; /** * Remove the focus from the currently focused item (both from the internal state and the DOM). */ removeFocusedItem = () => { const focusedItemId = focusSelectors.focusedItemId(this.store.state); if (focusedItemId == null) { return; } const itemMeta = itemsSelectors.itemMeta(this.store.state, focusedItemId); if (itemMeta) { const itemElement = this.store.items.getItemDOMElement(focusedItemId); if (itemElement) { itemElement.blur(); } } this.setFocusedItemId(null); }; /** * Event handler to fire when the `root` slot of the Tree View is focused. * @param {React.MouseEvent} event The DOM event that triggered the change. */ handleRootFocus = event => { if (event.defaultMuiPrevented) { return; } // if the event bubbled (which is React specific) we don't want to steal focus const defaultFocusableItemId = focusSelectors.defaultFocusableItemId(this.store.state); if (event.target === event.currentTarget && defaultFocusableItemId != null) { this.applyItemFocus(event, defaultFocusableItemId); } }; /** * Event handler to fire when the `root` slot of the Tree View is blurred. * @param {React.MouseEvent} event The DOM event that triggered the change. */ handleRootBlur = event => { if (event.defaultMuiPrevented) { return; } this.setFocusedItemId(null); }; }