feat(Menu): Render submenus out of DOM order#18490
Conversation
This PR renders each submenu in its own `Portal` by default to avoid issues with stacking contexts that has been a problem with v0. For this to work, it's necessary to stop depending of `focus` and `blur` events to close close child menus when the mouse enters a parent menu. This is solved by dispatching a custom DOM event that works similarly to `useOnClickOutside`. This will also be an important step to fix microsoft#18319 since each menu is rendered in its own `Portal` adds a fix for microsoft#18314
Perf Analysis (
|
| Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
|---|---|---|---|---|---|
| Avatar | mount | 869 | 881 | 5000 | |
| BaseButton | mount | 973 | 970 | 5000 | |
| Breadcrumb | mount | 2630 | 2696 | 1000 | |
| ButtonNext | mount | 553 | 546 | 5000 | |
| Checkbox | mount | 1670 | 1637 | 5000 | |
| CheckboxBase | mount | 1392 | 1396 | 5000 | |
| ChoiceGroup | mount | 5100 | 5019 | 5000 | |
| ComboBox | mount | 1005 | 1073 | 1000 | |
| CommandBar | mount | 10432 | 10359 | 1000 | |
| ContextualMenu | mount | 6582 | 6524 | 1000 | |
| DefaultButton | mount | 1242 | 1273 | 5000 | |
| DetailsRow | mount | 3983 | 4038 | 5000 | |
| DetailsRowFast | mount | 3983 | 4001 | 5000 | |
| DetailsRowNoStyles | mount | 3703 | 3726 | 5000 | |
| Dialog | mount | 2289 | 2189 | 1000 | |
| DocumentCardTitle | mount | 168 | 154 | 1000 | |
| Dropdown | mount | 3541 | 3574 | 5000 | |
| FocusTrapZone | mount | 1896 | 1909 | 5000 | |
| FocusZone | mount | 1786 | 1873 | 5000 | |
| IconButton | mount | 1868 | 1861 | 5000 | |
| Label | mount | 342 | 356 | 5000 | |
| Layer | mount | 1895 | 1901 | 5000 | |
| Link | mount | 481 | 488 | 5000 | |
| MakeStyles | mount | 1854 | 1844 | 50000 | |
| MenuButton | mount | 1653 | 1581 | 5000 | |
| MessageBar | mount | 2091 | 2153 | 5000 | |
| Nav | mount | 3420 | 3486 | 1000 | |
| OverflowSet | mount | 1060 | 1053 | 5000 | |
| Panel | mount | 2119 | 2090 | 1000 | |
| Persona | mount | 866 | 856 | 1000 | |
| Pivot | mount | 1456 | 1486 | 1000 | |
| PrimaryButton | mount | 1396 | 1371 | 5000 | |
| Rating | mount | 8230 | 8292 | 5000 | |
| SearchBox | mount | 1467 | 1449 | 5000 | |
| Shimmer | mount | 2685 | 2731 | 5000 | |
| Slider | mount | 2159 | 2146 | 5000 | |
| SpinButton | mount | 5265 | 5385 | 5000 | |
| Spinner | mount | 431 | 428 | 5000 | |
| SplitButton | mount | 3367 | 3353 | 5000 | |
| Stack | mount | 546 | 527 | 5000 | |
| StackWithIntrinsicChildren | mount | 1675 | 1685 | 5000 | |
| StackWithTextChildren | mount | 5033 | 5081 | 5000 | |
| SwatchColorPicker | mount | 11303 | 10831 | 5000 | |
| Tabs | mount | 1554 | 1558 | 1000 | |
| TagPicker | mount | 2714 | 2779 | 5000 | |
| TeachingBubble | mount | 12317 | 12705 | 5000 | |
| Text | mount | 472 | 444 | 5000 | |
| TextField | mount | 1451 | 1469 | 5000 | |
| ThemeProvider | mount | 1239 | 1197 | 5000 | |
| ThemeProvider | virtual-rerender | 614 | 630 | 5000 | |
| ThemeProviderNext | mount | 6966 | 6722 | 5000 | |
| Toggle | mount | 844 | 874 | 5000 | |
| buttonNative | mount | 115 | 118 | 5000 |
Perf Analysis (@fluentui/react-northstar)
Perf tests with no regressions
| Scenario | Current PR Ticks | Baseline Ticks | Ratio |
|---|---|---|---|
| GridMinimalPerf.default | 423 | 363 | 1.17:1 |
| AvatarMinimalPerf.default | 226 | 202 | 1.12:1 |
| AttachmentMinimalPerf.default | 179 | 167 | 1.07:1 |
| ImageMinimalPerf.default | 435 | 405 | 1.07:1 |
| SkeletonMinimalPerf.default | 414 | 386 | 1.07:1 |
| TextAreaMinimalPerf.default | 588 | 551 | 1.07:1 |
| DividerMinimalPerf.default | 403 | 380 | 1.06:1 |
| PortalMinimalPerf.default | 190 | 180 | 1.06:1 |
| RefMinimalPerf.default | 245 | 231 | 1.06:1 |
| ButtonMinimalPerf.default | 192 | 183 | 1.05:1 |
| FlexMinimalPerf.default | 316 | 302 | 1.05:1 |
| HeaderMinimalPerf.default | 417 | 398 | 1.05:1 |
| BoxMinimalPerf.default | 382 | 369 | 1.04:1 |
| CardMinimalPerf.default | 612 | 591 | 1.04:1 |
| HeaderSlotsPerf.default | 852 | 820 | 1.04:1 |
| ListWith60ListItems.default | 718 | 689 | 1.04:1 |
| MenuButtonMinimalPerf.default | 1778 | 1713 | 1.04:1 |
| PopupMinimalPerf.default | 617 | 593 | 1.04:1 |
| ReactionMinimalPerf.default | 437 | 421 | 1.04:1 |
| IconMinimalPerf.default | 695 | 667 | 1.04:1 |
| TextMinimalPerf.default | 376 | 360 | 1.04:1 |
| TooltipMinimalPerf.default | 1099 | 1056 | 1.04:1 |
| TreeMinimalPerf.default | 864 | 834 | 1.04:1 |
| ItemLayoutMinimalPerf.default | 1362 | 1319 | 1.03:1 |
| LayoutMinimalPerf.default | 419 | 406 | 1.03:1 |
| ListNestedPerf.default | 661 | 640 | 1.03:1 |
| RosterPerf.default | 1330 | 1292 | 1.03:1 |
| StatusMinimalPerf.default | 771 | 749 | 1.03:1 |
| VideoMinimalPerf.default | 701 | 682 | 1.03:1 |
| AttachmentSlotsPerf.default | 1238 | 1213 | 1.02:1 |
| ChatDuplicateMessagesPerf.default | 308 | 303 | 1.02:1 |
| CheckboxMinimalPerf.default | 2887 | 2830 | 1.02:1 |
| SegmentMinimalPerf.default | 398 | 390 | 1.02:1 |
| TableMinimalPerf.default | 455 | 444 | 1.02:1 |
| InputMinimalPerf.default | 1333 | 1324 | 1.01:1 |
| LabelMinimalPerf.default | 427 | 423 | 1.01:1 |
| MenuMinimalPerf.default | 950 | 937 | 1.01:1 |
| SplitButtonMinimalPerf.default | 4209 | 4152 | 1.01:1 |
| TableManyItemsPerf.default | 2133 | 2107 | 1.01:1 |
| CustomToolbarPrototype.default | 4012 | 3989 | 1.01:1 |
| AnimationMinimalPerf.default | 434 | 436 | 1:1 |
| ButtonSlotsPerf.default | 610 | 613 | 1:1 |
| ProviderMergeThemesPerf.default | 1761 | 1765 | 1:1 |
| RadioGroupMinimalPerf.default | 501 | 500 | 1:1 |
| SliderMinimalPerf.default | 1683 | 1684 | 1:1 |
| ToolbarMinimalPerf.default | 1015 | 1017 | 1:1 |
| ChatMinimalPerf.default | 660 | 667 | 0.99:1 |
| DatepickerMinimalPerf.default | 5573 | 5650 | 0.99:1 |
| DropdownManyItemsPerf.default | 736 | 744 | 0.99:1 |
| DropdownMinimalPerf.default | 3207 | 3229 | 0.99:1 |
| EmbedMinimalPerf.default | 4381 | 4439 | 0.99:1 |
| LoaderMinimalPerf.default | 735 | 740 | 0.99:1 |
| AccordionMinimalPerf.default | 166 | 170 | 0.98:1 |
| CarouselMinimalPerf.default | 494 | 503 | 0.98:1 |
| DialogMinimalPerf.default | 795 | 820 | 0.97:1 |
| ListCommonPerf.default | 680 | 701 | 0.97:1 |
| ListMinimalPerf.default | 542 | 560 | 0.97:1 |
| TreeWith60ListItems.default | 186 | 192 | 0.97:1 |
| ButtonOverridesMissPerf.default | 1779 | 1846 | 0.96:1 |
| ProviderMinimalPerf.default | 1064 | 1106 | 0.96:1 |
| AlertMinimalPerf.default | 278 | 299 | 0.93:1 |
| ChatWithPopoverPerf.default | 374 | 401 | 0.93:1 |
| FormMinimalPerf.default | 439 | 481 | 0.91:1 |
Asset size changes
Baseline commit: f17969bfe99f38432821266657c2664b891ebdc6 (build) |
| onClick: noop, | ||
| onMouseEnter: noop, | ||
| onMouseLeave: noop, | ||
| onContextMenu: noop, | ||
| onKeyDown: noop, |
There was a problem hiding this comment.
Since noop is quite a common used term and function, what about adding it to @fluentui/react-utilities?
| /** | ||
| * Name of the custom event | ||
| */ | ||
| export const MENU_ENTER_EVENT = 'fuimenuenter'; |
There was a problem hiding this comment.
Would be nice to have an RFC for making this a pattern for custom events that can be public.
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
|
🎉 Handy links: |
Pull request checklist
Tabbehaviour is not what is expected after moving toPortal#18314$ yarn changeDescription of changes
This PR renders each submenu in its own
Portalby default to avoidissues with stacking contexts that has been a problem with v0.
For this to work, it's necessary to stop depending of
focusandblurevents to close close child menus when the mouse enters a parent menu.
This is solved by dispatching a custom DOM event that works similarly to
useOnClickOutside. This will also be an important step to fix #18319since each menu is rendered in its own
Portaladds a fix for #18314Focus areas to test
(optional)