[utils] Explicitly register roving tab items with parent by mj12albert · Pull Request #48122 · mui/material-ui (original) (raw)

Migration doc: https://deploy-preview-48122--material-ui.netlify.app/material-ui/migration/upgrade-to-v9/#menu-and-menulist

Previews:

Notable changes:

1. Roving items explicitly opt in

Previously, the parent (e.g. MenuList) computes and manages child indexes, injecting ref and tabindex into each children. Now an additional useRovingTabIndexItem hook is available for roving items to explicitly register themselves with the parent. This way the parent doesn't have to scan children to determine out whether something is a roving item or not. (Also a child hook wouldn't have worked inside React.Children.map)

This makes muiSkipListHighlight unnecessary, and easier to support conditional rendering/Fragments inside Menus

The idea and mechanics here are borrowed from Base UI - registry map of roving item refs, DOM order tracking

2. Boundary between useRovingTabIndex and individual components

Previously the hook required components to "express" their specific focus rules through the hook call in the form of focusableIndex, shouldFocus. These often happened inside React.Children.map e.g. in MenuList, where props are created and injected into the children. I think this is mainly due to the constraint of scanning children, but with explicit registration this is no longer an issue.

Now the hook is more generic, component specific things like Tabs selection state and MenuList's "autofocus" rules stay within the component and no longer have to be "translated" through the hook.

Fixes #33268
Fixes #34218

⚠️ The bundle size report is inaccurate

I measured the @mui/utils/useRovingTabIndex after esbuilding it and and it should be 2653 gzip (+1506) (an increase, but there's another hook in there now). The mui-bot bundle size report shows -11.31% gzip for the utils package somehow because the export surface changed probably.
By comparison, Base UI's composites are about 7069 gzip.