Skip to content

[master-detail-layout] Overlay mode isn't triggered on first render #8969

@jouni

Description

@jouni

Description

In some cases, the overlay mode isn't triggered initially when the detail element is rendered in the layout. Only after the first resize event the correct mode is used.

Expected outcome

The overlay mode is used immediately when a detail element, which doesn't fit in the layout together with the master content, is added to the layout.

Minimal reproducible example

Replace the dev/master-detail-layout.html page with the following:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Master Detail Layout</title>
    <script type="module" src="./common.js"></script>
  </head>

  <body>
    <style>
      html,
      body {
        height: 100%;
      }

      vaadin-master-detail-layout {
        border: solid 1px #ccc;
      }

      vaadin-master-detail-layout[orientation='horizontal'] > [slot='detail'] {
        width: var(--detail-size);
      }

      vaadin-master-detail-layout[orientation='vertical'] > [slot='detail'] {
        height: var(--detail-size);
      }
    </style>

    <p>
      <vaadin-checkbox id="showDetail" label="Show detail" checked></vaadin-checkbox>
      <vaadin-checkbox id="detailSize" label="Set detail size"></vaadin-checkbox>
      <vaadin-checkbox id="detailMinSize" label="Set detail min-size"></vaadin-checkbox>
      <vaadin-checkbox id="masterSize" label="Set master size"></vaadin-checkbox>
      <vaadin-checkbox id="masterMinSize" label="Set master min-size"></vaadin-checkbox>
      <vaadin-checkbox id="containmentViewport" label="Use viewport containment"></vaadin-checkbox>
      <vaadin-checkbox id="vertical" label="Use vertical orientation"></vaadin-checkbox>
      <vaadin-checkbox id="maxWidth" label="Use max-width on the host"></vaadin-checkbox>
      <vaadin-checkbox id="maxHeight" label="Use max-height on the host"></vaadin-checkbox>
      <vaadin-checkbox id="forceOverlay" label="Force overlay"></vaadin-checkbox>
      <vaadin-checkbox id="stack" label="Set stack threshold"></vaadin-checkbox>
      <button id="set-small-detail">Set small detail</button>
      <button id="set-large-detail">Set large detail</button>
      <button id="set-test-detail">Set test detail</button>
      <button id="clear-detail">Clear detail</button>
    </p>

    <vaadin-master-detail-layout>
      <master-content></master-content>
      <detail-content slot="detail"></detail-content>
    </vaadin-master-detail-layout>

    <script type="module">
      // Enable feature flag
      window.Vaadin ||= {};
      window.Vaadin.featureFlags ||= {};
      window.Vaadin.featureFlags.masterDetailLayoutComponent = true;

      import '@vaadin/checkbox';
      import '@vaadin/master-detail-layout';
      import '@vaadin/master-detail-layout/test/helpers/master-content.js';
      import '@vaadin/master-detail-layout/test/helpers/detail-content.js';

      import '@vaadin/button';
      import '@vaadin/radio-group';
      import '@vaadin/icon';
      import '@vaadin/tooltip';

      import { css, html, LitElement } from 'lit';

      const layout = document.querySelector('vaadin-master-detail-layout');
      const detailContent = document.querySelector('detail-content');

      document.querySelector('#showDetail').addEventListener('change', (e) => {
        if (e.target.checked) {
          layout.append(detailContent);
        } else {
          detailContent.remove();
        }
      });

      document.querySelector('#detailSize').addEventListener('change', (e) => {
        layout.detailSize = e.target.checked ? '300px' : null;
      });

      document.querySelector('#detailMinSize').addEventListener('change', (e) => {
        layout.detailMinSize = e.target.checked ? '300px' : null;
      });

      document.querySelector('#masterSize').addEventListener('change', (e) => {
        layout.masterSize = e.target.checked ? '300px' : null;
      });

      document.querySelector('#masterMinSize').addEventListener('change', (e) => {
        layout.masterMinSize = e.target.checked ? '300px' : null;
      });

      document.querySelector('#containmentViewport').addEventListener('change', (e) => {
        layout.containment = e.target.checked ? 'viewport' : 'layout';
      });

      document.querySelector('#vertical').addEventListener('change', (e) => {
        layout.orientation = e.target.checked ? 'vertical' : 'horizontal';
      });

      document.querySelector('#maxWidth').addEventListener('change', (e) => {
        if (e.target.checked) {
          layout.style.maxWidth = '800px';
        } else {
          layout.style.maxWidth = '';
        }
      });

      document.querySelector('#maxHeight').addEventListener('change', (e) => {
        if (e.target.checked) {
          layout.style.maxHeight = '600px';
        } else {
          layout.style.maxHeight = '';
        }
      });

      document.querySelector('#forceOverlay').addEventListener('change', (e) => {
        layout.forceOverlay = e.target.checked;
      });

      document.querySelector('#stack').addEventListener('change', (e) => {
        layout.stackThreshold = e.target.checked ? '600px' : null;
      });

      document.querySelector('#set-small-detail').addEventListener('click', () => {
        const detail = document.createElement('detail-content');
        detail.style.setProperty('--detail-size', '200px');
        layout._setDetail(detail);
      });

      document.querySelector('#set-large-detail').addEventListener('click', () => {
        const detail = document.createElement('detail-content');
        detail.style.setProperty('--detail-size', '600px');
        layout._setDetail(detail);
      });

      document.querySelector('#set-test-detail').addEventListener('click', () => {
        const detail = document.createElement('mdl-view');
        layout._setDetail(detail);
      });

      document.querySelector('#clear-detail').addEventListener('click', () => {
        layout._setDetail(null);
      });

      window.mdlCount = 1;

      class MDLView extends LitElement {
        static get is() {
          return 'mdl-view';
        }

        createRenderRoot() {
          return this;
        }

        firstUpdated() {
          this._mdl = this.querySelector('vaadin-master-detail-layout');
          this._parentMdl = this.closest('vaadin-master-detail-layout');
          this._containmentSelect = this.querySelector('.containment');
          this._orientationSelect = this.querySelector('.orientation');
        }

        openDetail() {
          window.mdlCount++;
          this._mdl._setDetail(document.createElement('mdl-view'));
        }

        closeParentDetail() {
          this._parentMdl._setDetail(null);
        }

        render() {
          return html`
          <vaadin-master-detail-layout>
            <div class="master">
              <header>
                <vaadin-button aria-label="close detail view" class="close-btn" theme="tertiary contrast icon" @click=${this.closeParentDetail}>
                  <vaadin-icon icon="lumo:arrow-left"></vaadin-icon>
                  <vaadin-tooltip slot="tooltip" text="Close detail"></vaadin-tooltip>
                </vaadin-button>
                <h3>View ${window.mdlCount}</h3>
              </header>
              <vaadin-radio-group @change=${this._configChange} class="orientation" label="Orientation" theme="vertical" value="horizontal">
                <vaadin-radio-button value="horizontal" label="Horizontal"></vaadin-radio-button>
                <vaadin-radio-button value="vertical" label="Vertical"></vaadin-radio-button>
              </vaadin-radio-group>
              <vaadin-radio-group @change=${this._configChange} class="containment" label="Containment" theme="vertical" value="layout">
                <vaadin-radio-button value="layout" label="Layout"></vaadin-radio-button>
                <vaadin-radio-button value="viewport" label="Viewport"></vaadin-radio-button>
              </vaadin-radio-group>
              <br>
              <vaadin-button @click=${this.openDetail}>Open Nested Test View</vaadin-button>
            </div>
          </vaadin-master-detail-layout>
        `;
        }

        _configChange(e) {
          this._mdl[e.currentTarget.className] = e.target.value.match(/auto|none/) ? null : e.target.value;
        }
      }

      customElements.define(MDLView.is, MDLView);
    </script>
  </body>
</html>

Steps to reproduce

  • Open the page.
  • Resize the browser viewport width to 420px or less.
  • Click the Set test detail button.
  • The layout is using the default split mode, even though the Open Nested Test View button is overflowing the detail part.
  • Make the browser viewport smaller.
  • The layout is using the overlay mode, and the Open Nested Test View button is completely within the detail overlay.
Screen.Recording.2025-04-16.at.10.49.44.mov

Environment

Browsers

No response

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions