html icon indicating copy to clipboard operation
html copied to clipboard

API to get top layer elements

Open sanajaved7 opened this issue 2 years ago • 10 comments

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

sanajaved7 avatar Mar 27 '23 15:03 sanajaved7

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

annevk avatar Mar 27 '23 16:03 annevk

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 avatar Mar 27 '23 16:03 nt1m

@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.

sanajaved7 avatar Mar 27 '23 19:03 sanajaved7

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.

mfreed7 avatar Mar 27 '23 20:03 mfreed7

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.

Link2Twenty avatar Mar 05 '24 12:03 Link2Twenty

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.

emilio avatar Mar 11 '24 15:03 emilio

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.

keithamus avatar Mar 13 '24 10:03 keithamus

Dialog doesn't fire toggle events (see #9733) but you can observe the open attribute 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);

hirasso avatar Feb 04 '25 19:02 hirasso

Dialog doesn't fire toggle events (see #9733) but you can observe the open attribute 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

Link2Twenty avatar Feb 17 '25 16:02 Link2Twenty

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.

woody-li avatar Dec 10 '25 04:12 woody-li