-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(Popover): Support nesting #18148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Introduces `@fluentui/react-virtual-parents` as a utility library to set virtual parents. Integrates it as an optional feature in `usePopper`. The virtual parent feature will allow `Popovers` to be nested so that: * click outside works for the entire `Popover` stack * clicking in parent `Popovers` will close nested `Popovers`
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit aae1997:
|
Marking as draft again, to extend the solution to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please also port some unit tests for virtualParent
?
Asset size changes
Baseline commit: 644df65321367dd615548b2877293d5268595c2b (build) |
Perf Analysis (
|
Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
---|---|---|---|---|---|
Avatar | mount | 946 | 942 | 5000 | |
BaseButton | mount | 1025 | 1050 | 5000 | |
Breadcrumb | mount | 2816 | 2859 | 1000 | |
ButtonNext | mount | 561 | 563 | 5000 | |
Checkbox | mount | 1793 | 1773 | 5000 | |
CheckboxBase | mount | 1482 | 1511 | 5000 | |
ChoiceGroup | mount | 5352 | 5428 | 5000 | |
ComboBox | mount | 1168 | 1107 | 1000 | |
CommandBar | mount | 11099 | 11048 | 1000 | |
ContextualMenu | mount | 6839 | 6785 | 1000 | |
DefaultButton | mount | 1291 | 1300 | 5000 | |
DetailsRow | mount | 4081 | 4156 | 5000 | |
DetailsRowFast | mount | 4210 | 4160 | 5000 | |
DetailsRowNoStyles | mount | 3895 | 3912 | 5000 | |
Dialog | mount | 2310 | 2329 | 1000 | |
DocumentCardTitle | mount | 163 | 158 | 1000 | |
Dropdown | mount | 3622 | 3576 | 5000 | |
FocusTrapZone | mount | 1928 | 2020 | 5000 | |
FocusZone | mount | 1938 | 1989 | 5000 | |
IconButton | mount | 1948 | 2005 | 5000 | |
Label | mount | 387 | 380 | 5000 | |
Layer | mount | 1991 | 2018 | 5000 | |
Link | mount | 517 | 515 | 5000 | |
MakeStyles | mount | 1927 | 2045 | 50000 | |
MenuButton | mount | 1648 | 1673 | 5000 | |
MessageBar | mount | 2199 | 2220 | 5000 | |
Nav | mount | 3695 | 3597 | 1000 | |
OverflowSet | mount | 1160 | 1126 | 5000 | |
Panel | mount | 2286 | 2236 | 1000 | |
Persona | mount | 897 | 900 | 1000 | |
Pivot | mount | 1578 | 1585 | 1000 | |
PrimaryButton | mount | 1406 | 1435 | 5000 | |
Rating | mount | 8583 | 8745 | 5000 | |
SearchBox | mount | 1513 | 1512 | 5000 | |
Shimmer | mount | 2844 | 2989 | 5000 | |
Slider | mount | 2198 | 2184 | 5000 | |
SpinButton | mount | 5558 | 5500 | 5000 | |
Spinner | mount | 478 | 450 | 5000 | |
SplitButton | mount | 3502 | 3482 | 5000 | |
Stack | mount | 552 | 565 | 5000 | |
StackWithIntrinsicChildren | mount | 1699 | 1694 | 5000 | |
StackWithTextChildren | mount | 5192 | 5128 | 5000 | |
SwatchColorPicker | mount | 11239 | 11232 | 5000 | |
Tabs | mount | 1573 | 1533 | 1000 | |
TagPicker | mount | 2817 | 2775 | 5000 | |
TeachingBubble | mount | 12908 | 12886 | 5000 | |
Text | mount | 484 | 500 | 5000 | |
TextField | mount | 1576 | 1559 | 5000 | |
ThemeProvider | mount | 1328 | 1300 | 5000 | |
ThemeProvider | virtual-rerender | 658 | 667 | 5000 | |
ThemeProviderNext | mount | 7075 | 7023 | 5000 | |
Toggle | mount | 906 | 886 | 5000 | |
buttonNative | mount | 116 | 133 | 5000 |
Perf Analysis (@fluentui/react-northstar
)
Potential regressions comparing to master
Scenario | Current PR Ticks | Baseline Ticks | Ratio | Regression Analysis |
---|---|---|---|---|
AvatarMinimalPerf.default | 218 | 222 | 0.98:1 | analysis |
Perf tests with no regressions
Scenario | Current PR Ticks | Baseline Ticks | Ratio |
---|---|---|---|
ButtonMinimalPerf.default | 215 | 190 | 1.13:1 |
AttachmentMinimalPerf.default | 198 | 177 | 1.12:1 |
SkeletonMinimalPerf.default | 441 | 401 | 1.1:1 |
CarouselMinimalPerf.default | 546 | 502 | 1.09:1 |
HeaderMinimalPerf.default | 444 | 413 | 1.08:1 |
LabelMinimalPerf.default | 462 | 430 | 1.07:1 |
ReactionMinimalPerf.default | 454 | 424 | 1.07:1 |
TableMinimalPerf.default | 481 | 451 | 1.07:1 |
TextMinimalPerf.default | 421 | 394 | 1.07:1 |
MenuMinimalPerf.default | 997 | 940 | 1.06:1 |
SegmentMinimalPerf.default | 410 | 385 | 1.06:1 |
VideoMinimalPerf.default | 734 | 692 | 1.06:1 |
AccordionMinimalPerf.default | 183 | 175 | 1.05:1 |
TooltipMinimalPerf.default | 1116 | 1067 | 1.05:1 |
CardMinimalPerf.default | 643 | 617 | 1.04:1 |
DropdownManyItemsPerf.default | 791 | 764 | 1.04:1 |
ItemLayoutMinimalPerf.default | 1495 | 1438 | 1.04:1 |
LayoutMinimalPerf.default | 426 | 411 | 1.04:1 |
ListCommonPerf.default | 745 | 715 | 1.04:1 |
PortalMinimalPerf.default | 174 | 167 | 1.04:1 |
RadioGroupMinimalPerf.default | 512 | 491 | 1.04:1 |
IconMinimalPerf.default | 721 | 691 | 1.04:1 |
ToolbarMinimalPerf.default | 1112 | 1072 | 1.04:1 |
DatepickerMinimalPerf.default | 6224 | 6043 | 1.03:1 |
DividerMinimalPerf.default | 427 | 414 | 1.03:1 |
FlexMinimalPerf.default | 328 | 318 | 1.03:1 |
GridMinimalPerf.default | 396 | 384 | 1.03:1 |
ProviderMinimalPerf.default | 1114 | 1084 | 1.03:1 |
StatusMinimalPerf.default | 804 | 783 | 1.03:1 |
TreeMinimalPerf.default | 908 | 878 | 1.03:1 |
AnimationMinimalPerf.default | 477 | 467 | 1.02:1 |
BoxMinimalPerf.default | 397 | 388 | 1.02:1 |
ChatDuplicateMessagesPerf.default | 328 | 323 | 1.02:1 |
CheckboxMinimalPerf.default | 3059 | 2997 | 1.02:1 |
MenuButtonMinimalPerf.default | 1803 | 1768 | 1.02:1 |
CustomToolbarPrototype.default | 4123 | 4059 | 1.02:1 |
AttachmentSlotsPerf.default | 1268 | 1257 | 1.01:1 |
DialogMinimalPerf.default | 827 | 821 | 1.01:1 |
DropdownMinimalPerf.default | 3386 | 3343 | 1.01:1 |
InputMinimalPerf.default | 1376 | 1368 | 1.01:1 |
ListMinimalPerf.default | 571 | 564 | 1.01:1 |
SliderMinimalPerf.default | 1698 | 1683 | 1.01:1 |
SplitButtonMinimalPerf.default | 4206 | 4168 | 1.01:1 |
TableManyItemsPerf.default | 2207 | 2180 | 1.01:1 |
TextAreaMinimalPerf.default | 599 | 593 | 1.01:1 |
TreeWith60ListItems.default | 188 | 187 | 1.01:1 |
ButtonOverridesMissPerf.default | 1851 | 1859 | 1:1 |
ButtonSlotsPerf.default | 620 | 621 | 1:1 |
ChatMinimalPerf.default | 708 | 709 | 1:1 |
EmbedMinimalPerf.default | 4537 | 4537 | 1:1 |
ImageMinimalPerf.default | 435 | 437 | 1:1 |
LoaderMinimalPerf.default | 758 | 760 | 1:1 |
AlertMinimalPerf.default | 319 | 322 | 0.99:1 |
HeaderSlotsPerf.default | 881 | 892 | 0.99:1 |
FormMinimalPerf.default | 466 | 477 | 0.98:1 |
ListWith60ListItems.default | 720 | 738 | 0.98:1 |
ProviderMergeThemesPerf.default | 1743 | 1770 | 0.98:1 |
PopupMinimalPerf.default | 620 | 639 | 0.97:1 |
RefMinimalPerf.default | 256 | 264 | 0.97:1 |
ChatWithPopoverPerf.default | 394 | 416 | 0.95:1 |
ListNestedPerf.default | 618 | 655 | 0.94:1 |
RosterPerf.default | 1250 | 1351 | 0.93:1 |
Introduces `@fluentui/react-virtual-parents` as a utility library to set virtual parents. Integrates it as an optional feature in `usePopper`. The virtual parent feature will allow `Popovers` to be nested so that: * click outside works for the entire `Popover` stack * clicking in parent `Popovers` will close nested `Popovers`
@behowell can you take a look at the tooltip snapshot updates ? It's caused by |
*/ | ||
export function getVirtualParent(child: HTMLElement): HTMLElement | undefined { | ||
let parent: HTMLElement | undefined; | ||
if (child && isVirtualElement(child)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to check child
here?
It is HTMLElement
and it's isVirtualElement
's responsibility to check it's truthy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it's unnecessary
React.useEffect(() => { | ||
if (state.virtualParentRootRef.current && state.mountNode) { | ||
setVirtualParent(state.mountNode, state.virtualParentRootRef.current); | ||
} | ||
}, [state.virtualParentRootRef, state.mountNode]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't re-run if virtualParentRootRef.current
changes. You might be able to make it work by making virtualParentRootRef a function that gets called whenever the ref is set... but then you'd also want to update the virtual parent whenever mountNode
changes.
That said, presumably setVirtualParent
is cheap right? Maybe it's simpler to make this effect run after every render (remove the deps).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure if virtualParentRootRef.current
would ever change, since it is always set internally into that span that users will never have access to.
That inacessible slot always renders even during server render in SSR or in the client when the Portal
can't be rendered.
🎉 Handy links: |
🎉 Handy links: |
🎉 Handy links: |
Pull request checklist
$ yarn change
Description of changes
Introduces
virtual-parents
toreact-portal
. Portal will always be rendered with a hiddenspan
so thatelementContains
will work for allPortals
in the same React treeThe virtual parent feature will allow
Popovers
,Menus
andTooltips
to be nested so that:Focus areas to test
(optional)