Layer & Portals
Layer
previously (< OUFR v6.66.0) used React's unstable_renderSubtreeIntoContainer
, which will soon be removed.
Compared to unstable_renderSubtreeIntoContainer
, React Portals introduced in OUFR v6.66.0 dispatches events in a way that respects virtual hierarchy rather than DOM hierarchy. This means that events will now trickle down and bubble up from portals through ancestors that previously would have not received these events.
Since pre-Portals Layer
events never bubbled up to its ancestors prior to React Portals, Layer
's use of React portals has been implemented with event blocking added to more closely simulate Layer
's previous behavior. Event bubbling is controlled via a new Layer
prop eventBubblingEnabled
and is disabled by default for backwards compatibility.
However, React portals do introduce a couple of known behavior changes regarding the following events:
- Capture events (for localized, non-window capture handlers)
-
onMouseEnter
/onMouseLeave
These behavioral changes will affect both app code and components that have any portals as part of their hierarchy.
If you have app or component code with localized, non-window capture or onMouseEnter
/ onMouseLeave
handlers and that code has any of the following components as children, you should be aware of these changes.
- Callout
- ComboBox
- ContextualMenu
- DatePicker
- Dropdown
- ExpandingCard
- Keytip
- TeachingBubble
- Tooltip
- Coachmark
- Layer
- Modal
- Dialog
- Panel
Layer
can block events that bubble up and prevent them from traversing up the hierarchy. However, capture events trickle down and cannot be easily blocked or controlled by Layer
. Since Portals more accurately respect virtual hierarchy, capture events will now trickle down through content to any components contained in the Layer
. (This didn't happen with old Layer because these events just traversed directly to the Layer subtree attached to root.)
The effect is that when any of the components listed above are interacted with, existing capture handlers in their ancestors will now fire when they haven't previously. In cases where this causes undesired behavior, a utilities
helper portalContainsElement
was added to help work around this specific example:
For example, one case of undesired behavior in Fabric was in the BaseButton
component. This new utility was used to properly manage focus for expanded SplitButton dropdown menus:
private _onSplitContainerFocusCapture = (ev: React.FocusEvent<HTMLDivElement>) => {
const container = this._splitButtonContainer.current;
// If the target is coming from the portal we do not need to set focus on the container.
if (!container || (ev.target && portalContainsElement(ev.target, container))) {
return;
}
...
};
When any of the components listed above are moused over, every onMouseEnter
handler along the subtree from the common ancestor of the component left down to the Layered component will be triggered. (This didn't happen with old Layer because these events just traversed directly to / from the Layer
subtree attached to root.)
For example, if a Tooltip
contains a Modal
and the Modal
is open, the Tooltip
will appear over the Modal when the mouse enters the browser window. This happens because Tooltip
's onMouseEnter
handler is now triggered with portals where it wasn't before.
The same portalContainsElement
utility mentioned for capture handlers can be used in these scenarios. For example, to prevent Tooltips
from appearing over child Modals
, the following code was added to TooltipHost
:
private _onTooltipMouseEnter = (ev): void => {
...
if (ev.target && portalContainsElement(ev.target as HTMLElement, this._getTargetElement())) {
// Do not show tooltip when target is inside a portal relative to TooltipHost.
return;
}
...
};
If both TooltipHost
and ev.target
are contained within the same portal, the Tooltip
will continue to appear as expected.
Likewise, onMouseLeave
used to be generated when Layered components rendered. Now that Layered components are more accurately viewed as part of the hierarchy, onMouseLeave
events may not get generated when rendering Layered content. Additionally, onMouseLeave
events will now get generated for the entire root->content->Layer->Layered component subtree when the mouse leaves the browser window or any point in that subtree.
- FAQ - Fabric and Stardust to Fluent UI
-
@fluentui/react
Version 9 -
@fluentui/react
Version 8 - Contributing to the
7.0
branch - How to apply themes (version 7/8)
- Planning and development process (for work by the core team)
- Conducting meetings Style guide
- Keeping up with review requests
- RFC review process
- Setup (configuring your environment)
- Fluent UI React version 7/8
- CLA
- Overview
- Repo structure
- Development process
- Contributing to previous versions
- API Extractor
- Build command changes made in early 2020
- Component implementation guide
- Creating a component
- Implementation Best Practices
- Theming
- Documenting
- Styling (old approach)
- Overview
- Testing with Jest
- E2E testing (Cypress)
- Visual testing (Screener)
- Accessibility review checklist