@mui/x-tree-view
Version:
The community edition of the MUI X Tree View components.
113 lines (111 loc) • 3.65 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import { useStableCallback } from '@base-ui/utils/useStableCallback';
import { useStoreEffect } from '@mui/x-internals/store';
import { focusSelectors } from "./useTreeViewFocus.selectors.js";
import { expansionSelectors } from "../useTreeViewExpansion/useTreeViewExpansion.selectors.js";
import { itemsSelectors } from "../useTreeViewItems/useTreeViewItems.selectors.js";
export const useTreeViewFocus = ({
instance,
params,
store
}) => {
const setFocusedItemId = useStableCallback(itemId => {
const focusedItemId = focusSelectors.focusedItemId(store.state);
if (focusedItemId === itemId) {
return;
}
store.set('focus', _extends({}, store.state.focus, {
focusedItemId: itemId
}));
});
const isItemVisible = itemId => {
const itemMeta = itemsSelectors.itemMeta(store.state, itemId);
return itemMeta && (itemMeta.parentId == null || expansionSelectors.isItemExpanded(store.state, itemMeta.parentId));
};
const innerFocusItem = (event, itemId) => {
const itemElement = instance.getItemDOMElement(itemId);
if (itemElement) {
itemElement.focus();
}
setFocusedItemId(itemId);
if (params.onItemFocus) {
params.onItemFocus(event, itemId);
}
};
const focusItem = useStableCallback((event, itemId) => {
// If we receive an itemId, and it is visible, the focus will be set to it
if (isItemVisible(itemId)) {
innerFocusItem(event, itemId);
}
});
const removeFocusedItem = useStableCallback(() => {
const focusedItemId = focusSelectors.focusedItemId(store.state);
if (focusedItemId == null) {
return;
}
const itemMeta = itemsSelectors.itemMeta(store.state, focusedItemId);
if (itemMeta) {
const itemElement = instance.getItemDOMElement(focusedItemId);
if (itemElement) {
itemElement.blur();
}
}
setFocusedItemId(null);
});
// Whenever the items change, we need to ensure the focused item is still present.
useStoreEffect(store, 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) {
setFocusedItemId(null);
return;
}
innerFocusItem(null, defaultFocusableItemId);
});
const createRootHandleFocus = otherHandlers => event => {
otherHandlers.onFocus?.(event);
if (event.defaultMuiPrevented) {
return;
}
// if the event bubbled (which is React specific) we don't want to steal focus
const defaultFocusableItemId = focusSelectors.defaultFocusableItemId(store.state);
if (event.target === event.currentTarget && defaultFocusableItemId != null) {
innerFocusItem(event, defaultFocusableItemId);
}
};
const createRootHandleBlur = otherHandlers => event => {
otherHandlers.onBlur?.(event);
if (event.defaultMuiPrevented) {
return;
}
setFocusedItemId(null);
};
return {
getRootProps: otherHandlers => ({
onFocus: createRootHandleFocus(otherHandlers),
onBlur: createRootHandleBlur(otherHandlers)
}),
publicAPI: {
focusItem
},
instance: {
focusItem,
removeFocusedItem
}
};
};
useTreeViewFocus.getInitialState = () => ({
focus: {
focusedItemId: null
}
});
useTreeViewFocus.params = {
onItemFocus: true
};