Migration: 2023/8/18 #

As of the 0.37.0 release of the Spectrum Web Components library, we will be leveraging a new version of our Overlay API. We've done our best to ensure a smooth transition from one version of the API to the next, including adding extended support for the argument signature from the previous version. In this way, consumption of elements from the library (e.g. <overlay-trigger>, <sp-picker>, <sp-tooltip>, et al.) or the imperative Overlay API (e.g. Overlay.open()) should continue affording a close facsimile of the functionality that was provided by the previous version. Under the covers, many important changes have been made and there are several things you can do to prepare within your application's lifecycle.

<active-overlay> is no longer part of the API #

If you had previously done work in your application to interact directly with overlaid content from the application level by making CSS or JS reference to <active-overlay> elements, there will be changes required in your application.

Remove the open attribute from content that is meant to be overlaid. #

The open property will be addressed directly by the Overlay API itself. Elements wanting to manage their visual transition from the closed to open state should respond reactively to that change. Management of this attribute by the parent application could prevent those transitions from occurring as expected in elements provided by the Spectrum Web Components library.

DO exclude open attributes from slot="*-content" children of <overlay-trigger> elements

<overlay-trigger>
    <sp-button slot="trigger">Trigger</sp-button>
    <sp-popover slot="click-content" placement="bottom">
        <sp-dialog no-divider>Popover for the trigger</sp-dialog>
    </sp-popover>
    <sp-tooltip slot="hover-content" placement="right">
        Tooltip for the trigger
    </sp-tooltip>
    <!-- etc. -->
</overlay-trigger>

Remove usage of placement="none" in both declarative and imperative API usages #

placement="none" or placement: 'none' was previously leveraged to outline an overlay that would take the full size of the viewport. This responsibility is now fulfilled by assigning this value as undefined, or, better yet, by not including it at all.

DO omit placement when not specifically relating the overlaid content to its target with the imperative API

this.closeHoverOverlay = Overlay.open(
    triggerReference,
    'modal',
    contentReference,
    {
        delayed: false,
        offset: 0,
        receivesFocus: 'auto',
    }
);

Ensure that your consumption of sp-opened and sp-closed events are typed #

Some of the internal properties of these events are changing, see interaction: 'auto' | 'hint' | 'manual' | 'modal' | 'page'. Ensuring you are consuming these types will allow Typescript to support your upgrade from one version of the API to the next.

DO use the OverlayOpenCloseDetail type when listening for these Custom Events

html`
    <element-containing-an-overlay
        @sp-closed=${(event: OverlayOpenCloseDetail) => {
            if (event.detail.interaction === 'auto') {
                // Do something when the event was dispatched for an overlay with type "auto".
            }
        }}
        @sp-opened=${(event: OverlayOpenCloseDetail) => {
            if (event.detail.interaction === 'modal') {
                // Do something when the event was dispatched for an overlay with type "modal".
            }
        }}
    ></element-containing-an-overlay>
`;

Prepare for descendant overlays to exist in the same DOM tree #

Previously, the reparenting of overlay content prevented sp-opened and sp-closed events from propagating through the overlay's ancestor DOM tree. Going forward these events will propagate in a more native manner meaning that an ancestor will have the opportunity to hear and respond to ALL sp-opened and sp-closed events for all of its descendant overlays.

DO be sure to gate your listeners if your experience stacks multiple overlays (see submenus) within each other.

function handleSpOpened(event: OverlayOpenCloseDetail) {
    // Return if the `sp-opened` event was not dispatched from the element to which this listener is attached.
    if (event.target !== event.currentTarget) return;
}

Consider leveraging the API declaratively #

The new Overlay API will continue to surface the Overlay.open() method to more readily support migration from previous verions. However, imperatively interacting with the API in the way will continue to require that the content you wish to overlay be reparented, which can have unexpected side effects. If you begin leveraging the V2 signature of the API, you content will be reparented into an <sp-overlay> element, and, rather than immediately placing the element into the page, it will be returned so that you can decide where you would like to append this content to your application. While continuing to leverage the V1 signature, not only will you content be reparented into an <sp-overlay> element, but that element will be inserted immediately after the trigger element that you have provided in support of applying your overlaid content to the tab order of your content in a predictable manner.

Either of these processes can have negative effects on the application of CSS, the way that events will propagate through your application, and the values returned from DOM selections APIs (like querySelector(...)). Leverage an <sp-overlay> directly in your application for full control over where the overlaid content will live in the DOM. Choosing a static location in your DOM for the <sp-overlay> element controlling your overlay will not only does this normalize interactions with CSS, events, or DOM selection APIs, but also empower you to make more sustainable decisions as to tab order by which keyboard and screen reader users will interact with your content.

Explanation #

The new version of the Overlay API no longer relies on portalling (moving content to the end of the <body> element) to defeat CSS clipping and stacking. While this approach was good at overcoming those realities, the reparenting of the overlaid content required to complete this technique had complex performance costs, due to the constant reorientation of elements to a new parent, and broke encapsulation by moving the content ourside of the shadow DOM in which is was originally placed. This made it difficult to take full control of styling and delivering overlaid content. Instead, the new API will leverage <dialog> elements and their showModal() method for modal overlays, and the popover attribute along with its showPopover() method. Both of these APIs lift content onto the top layer of the browser, which provides a full guarantee against CSS clipping and stacking interrupting the content addressed to this layer. Be aware that content on this layer is managed as a strict stack, so the element added last will always be "on top", regardless of any additional CSS you may apply. A discussion around additional features that could address this reality is ongoing; please jump in with your thoughts if you have them.

While the <dialog> element is widely supported by browsers, the popover attribute is still quite new. There is strong consensus on the API and stable implementations are starting to ship today, However, there will continue to be browsers that do not support this API for some time. To support these browsers, the API will now leverage a position: fixed approach to defeat CSS clipping and stacking. This approach has less of a guarantee in overcoming these realities than what was being used previously and may require intervention on your part.