paper-dialog icon indicating copy to clipboard operation
paper-dialog copied to clipboard

paper-dialog appears behind backdrop when inside a <app-header-layout>

Open simonfuhrmann opened this issue 8 years ago • 19 comments

Description

When opening a <paper-dialog> that is nested inside <app-header-layout>, its backdrop (e.g., when using attributes with-backdrop or modal) appears on top of the dialog, rendering the dialog inoperable.

The z-index of the dialog and backdrop element are in the correct order.

Defining all dialogs outside of <app-header-layout> is generally not possible in more complex UIs. Usually the <app-header-layout> appears at a top level component, nesting the main app components. If components further down the DOM need to define dialogs, they will basically end up not working.

Expected outcome

Backdrop should appear behind the dialog.

Actual outcome

Backdrop appears in front of the dialog. The dialog is inoperable. Clicking anywhere closes the dialog. In modal mode, the application is stuck forever as a dialog cannot be closed by clicking on the backdrop.

Steps to reproduce

Create an app layout as follows and add dialogs.

<app-header-layout fullbleed>
  <app-header slot="header">
    <app-toolbar>
      <div main-title>My main title header</div>
    </app-toolbar>
  </app-header>
  <!-- Dialogs here don't work -->
</app-header-layout>
<!-- Dialogs here work -->

Browsers Affected

Only tested on Chrome 59.0.3071.104 (Official Build) (64-bit)

Related issues

https://github.com/PolymerElements/paper-dialog/issues/7

simonfuhrmann avatar Jun 29 '17 23:06 simonfuhrmann

In #7, there was a workaround that globally moved all <paper-dialog> elements to a DOM position outside of the app layout (here, app-header-layout, back then paper-header-panel). The workaround is relying on querySelectorAll, and as far as I understand, this workaround doesn't work anymore with shadow dom.

simonfuhrmann avatar Jun 29 '17 23:06 simonfuhrmann

Unfortunately we can't fix this problem with the current overlay system, here more details on why.

The best solution you have is to declare dialogs in the right stacking context.

If you can afford loosing style encapsulation, you can reparent the dialogs to document.body before you open them, e.g. http://jsbin.com/vukazer/2/edit?html,output

valdrinkoshi avatar Jun 30 '17 00:06 valdrinkoshi

Are you saying it is not possible to have any dialogs with backdrop in an app layout? This seems like a severe limitation, especially when the workaround only works without local styles. Since you marked this with wontfix, will there be no solution to this problem in Polymer 2.0 in the foreseeable future?

In fact, I do have local styles that I want to preserve. Are there other workarounds?

simonfuhrmann avatar Jun 30 '17 16:06 simonfuhrmann

@simonfuhrmann you can use app-drawer and app-header and create wrapper aroun them, similar to app-drawer-layout but without setting z-index.

web-padawan avatar Jun 30 '17 16:06 web-padawan

@web-padawan, thanks, in fact, not using app-drawer-layout seems to be the easiest solution for me.

simonfuhrmann avatar Jun 30 '17 17:06 simonfuhrmann

@simonfuhrmann the issue is not with app-drawer-layout really, but a missing feature of the web platform - we cannot add elements on the Top Layer of the document. Today, any element that creates a new stacking context will "trap" its contents within it. The concept of Blocking Elements stack is in the works, which will finally allow us to add arbitrary elements in the Top Layer. I've been working on possible workarounds for this issue in iron-overlay-behavior (see this PR), and finally opted for a new overlay system, iron-overlay.

Unfortunately there's no silver bullet so far. The safest option is to declare the dialog in <body>.

valdrinkoshi avatar Jun 30 '17 17:06 valdrinkoshi

@valdrinkoshi, I understand.

But I can't declare the dialog in the body, because of how the app is structured (i.e., the dialog is defined in a deeply nested element). I also cannot move the dialog using document.body.appendChild(this.$.dialog), as it relies on local styling.

Although this may not be an issue of add-drawer-layout, removing the element and re-implementing what I need is the easiest fix that actually works on my side.

simonfuhrmann avatar Jun 30 '17 21:06 simonfuhrmann

@simonfuhrmann @valdrinkoshi

I just became aware of a workaround released by IswPolymerElements called <isw-dialog>.

I have not used it yet, so I can not vouch for it. But the demo looks encouraging.

Edit: I tried <isw-dialog> and it did not perform as expected. But I did remove the modal attribute from <paper-dialog> and that workaround will be temporarily sufficient for my purposes for now until the full solution is implemented that will allow me to use the modal feature again.

maria-le avatar Sep 04 '17 08:09 maria-le

@maria-le any dialog using paper-dialog-behavior should behave the same.

The only exception known to me is <vaadin-dialog> which uses the same approach as prototyped in <iron-overlay>: connecting overlay as direct body child.

web-padawan avatar Sep 04 '17 12:09 web-padawan

@maria-le Can you provide some feedback about the problem you ran into?

Edit: The idea of the <isw-dialog> is to define the dialog somewhere in the right stacking context and control it from inside your view via <isw-dialog-remote>. I know it is uncomfortable, but at least this allows you to control the dialog in the context you use them, without the need for manual controller/mediate communication.

Remote and Dialog are communicating via events on document, like <iron-signals> does.

rene-lindner-isw avatar Sep 08 '17 13:09 rene-lindner-isw

any progress on this issue :(

jukbot avatar Jan 21 '18 08:01 jukbot

@jukbot , to workaround this you could try adding a function that will move dialog's backdrop before dialog itself, like so:

const overlay = document.querySelector('iron-overlay-backdrop');
this.parentNode.insertBefore(overlay, this);

This has to be fired on iron-overlay-opened. Additionally, you should remove the backdrop on iron-overlay-closed:

this.parentNode.removeChild(document.querySelector('iron-overlay-backdrop'))

this refers to element that contains paper-dialog

Pietfro avatar Mar 15 '18 23:03 Pietfro

The problem with this workaround is that when you fire events from your dialog they will end up in different place than someone might expect I believe?

ergo avatar Mar 16 '18 00:03 ergo

+1

Pankraz76 avatar Mar 28 '18 11:03 Pankraz76

+1, with the entire goal of webcomponents being isolated and relatively loosely coupled components, having to move the dialog outside of the app-header-layout kinda destroys the intent.

ruckc avatar Apr 12 '18 01:04 ruckc

I have just found a (rather complex) work-around that works with (my setup of) app-header-layout/app-header—as-is, without modifications—in most situations. (i.e. every state except scrolled + revealed).

In a nutshell: remove the z-index from app-header as long as it's not fully scrolled away, and re-apply once it is. Then work around non-clickable contents using CSS' pointer-events property.

The only way I've found for getting the visible height involves observing CSS transform, so the actual code gets rather convoluted:

<template>
  <style>
    app-header-layout {
      pointer-events: none;
    }

    app-header {
      z-index: 0;
      pointer-events: auto;
    }

    app-header.scrolled {
      z-index: 1;
    }

    #content {
      pointer-events: auto;
    }
  </style>
  
    <app-header-layout id="headerLayout" class="header">
        <app-header id="header" slot="header" reveals condenses effects="waterfall">
          ...
        </app-header>
        <div id="content">
           ...
        </div>
    </app-header-layout>
  </template>
  <script>
    Polymer({
    ...
    attached() {
        const observer = new MutationObserver(this._handleStyleMutation.bind(this));
        observer.observe(this.$.header, {
          attributes: true,
          attributeOldValue: true,
          attributeFilter: ['style']
        });
        // create scroll-eventListener, too, throttled by 100ms
     },
     _handleStyleMutation(mutation) {
        const firstMutation = mutation[0];
        const cssStyleDeclaration = firstMutation.target[firstMutation.attributeName];
        const transform = cssStyleDeclaration.transform;
        if (!transform) {
          return;
        }

        const matches = transform.match(/translate3d\((.+)px, (.+)px, (.+)px\)/);
        if (matches.length <3) {
          return;
        }
        this._headerTop = parseFloat(matches[2], 10);
      },
    _handlePageScrollThrottled(evt) {
        this.$.header.classList.toggle('scrolled', window.scrollY >= this.visibleHeaderHeight);
      },
    get visibleHeaderHeight() {
        const height = this.$.header.getBoundingClientRect().height;
        return height + (this._headerTop || 0);
      }
    ...
  </script>

craPkit avatar May 25 '18 11:05 craPkit

In case someone is interested, we recently published a Material theme preview for Vaadin components, so that <vaadin-dialog> can be now used with a Material Theme, see examples. This component gets teleported as I mentioned in the above comment, so it will bypass the app-layout limitations.

web-padawan avatar May 25 '18 12:05 web-padawan

How about overriding app layout elements to make them NOT use shadow dom? Would that solve the issue? Since polymer 2.x this is possible.

ergo avatar May 25 '18 12:05 ergo

This behaviour seems to (almost) contravene the specifications for Material Design parent-child hierarchical transitions as described here: https://material.io/design/navigation/navigation-transitions.html#hierarchical-transitions. I say almost because I'm not finding anything specifically describing the navigation bar overlay, but I do see many examples of full screen dialog screens.

carlardron avatar Oct 26 '18 11:10 carlardron