API to get top layer elements
The OddBird team is currently building a polyfill for CSS anchor positioning.
To support anchoring top layer elements to each other, it would be very helpful to have a programmatic way to get the order of the elements in the top layer set. This is particularly useful to prevent a top layer target element from anchoring to a succeeding top layer anchor element. See this WPT for an example.
This issue is similar to #8783 but we're interested in all the elements of the top layer set to access their order.
cc: @jgerigmeyer @mfreed7
This is an interesting suggestion, but the bar for adding an API is higher. We don't add public API surface just for tests either, for instance.
cc @nt1m
I'm rather opposed to this, I think something being in the top layer or not should remain an implementation detail of the individual APIs using it.
@nt1m to clarify a bit further, my thinking is that if browsers are (or will be soon) allowing ways to inspect the top layer and the order of the elements in the top layer, it would be great to have a way to get that same data with something like getTopLayerElements.
I could see non-test use cases also, such as a popover component wanting to make sure it's topmost, and if not, closing and re-showing the popover to put it on top. You wouldn't want to do that unless you knew it was necessary, to avoid flickering and starting animations and such.
One constraint that I think should be a hard constraint, if we add such an API: don't reveal elements in the top layer. You can instead "ask" whether one top layer element is above another, such as:
element1.isAboveInTopLayer(element2);
so that you'd have to already have references to element1 and element2. Otherwise this would be an easy place to accidentally reveal shadow-contained top layer elements, for example.
such as a popover component wanting to make sure it's topmost, and if not, closing and re-showing the popover to put it on top.
This is exactly the use case I was looking to solve when I found my way here. Ideally a toast container should be able to listen for a top-layer event on the document, check to see if it is still at the topmost layer and do some action depending on the result.
Something that would perhaps be a bit more consistent with existing APIs would be something like DocumentOrShadowRoot.topmostTopLayerElement (or something, naming TBD), where the API would appropriately re-target much like fullscreenElement or activeElement do.
Would that help with the use case above? Or is there something that really needs "all the elements in the top layer, in order"?
We fire popover toggle events and fullscreen events. I don't recall, does <dialog> fire relevant events too? It seems with that you could track the top layer as well.
Dialog doesn't fire toggle events (see https://github.com/whatwg/html/issues/9733) but you can observe the open attribute and check .matches(':modal') to determine if it is now on the top layer.
Dialog doesn't fire toggle events (see #9733) but you can observe the
openattribute and check.matches(':modal')to determine if it is now on the top layer.
Beautiful! I just used this in one of my PlayWright tests:
// Check if the dialog is in the top layer
expect(await page.locator('dialog[open]').evaluate(el => el.matches(':modal'))).toBe(true);
Dialog doesn't fire toggle events (see #9733) but you can observe the
openattribute and check.matches(':modal')to determine if it is now on the top layer.
Great to see this is no longer the case we can now create a getTopLayerElements by doing something as simple as this.
const topLayerElements = new Set();
document.addEventListener(
"toggle",
({ target }) => {
if (
!(target instanceof HTMLDialogElement || target.hasAttribute("popover"))
)
return;
if (target.matches(":modal, :popover-open") && document.contains(target)) {
topLayerElements.add(target);
} else {
topLayerElements.delete(target);
}
},
{ capture: true }
);
const observer = new MutationObserver((mutations) => {
const nodes = mutations.flatMap(({ removedNodes }) => [...removedNodes]);
for (const node of nodes) topLayerElements.delete(node);
});
observer.observe(document.body, { childList: true, subtree: true });
window.getTopLayerElements = () => [...topLayerElements];
Though doing it this was still feels a little hackier than I like.
https://codesandbox.io/p/sandbox/rs6mzz
The toggle may be a workaround for it.
But doesn't apply to shadowDom, the event isn't composed.
I think we still need some API:
document.topLayerElements: Element[]
Get all elements in the top layer. Also use it for check whether an element is top-most.
toplayerchange event
Fired when the list changed. Use case: keep an element always at the top-most.
Name TBD: some way to bring an element to the top-most of the top layer.
For example: popover
For now, it needs to hidePopover and showPopover again to make it top-most.
As mentioned above, it will lose the state, animation, etc.
So need API that similar to moveBefore.