DauleDK
DauleDK

Reputation: 3453

How do I configure a material cdk overlay position strategy that works great, both on big and small screens?

Question

How do I configure a material cdk overlay position strategy that works great, on big and small screens?

Goal

My objective is to create an overlay with the Angular overlay CDK, that follow these rules:

What works 😎

I have accomplished some of the above requirements, as you can see here the positioning works great on devices with enough vertical space (in this case 512px):

enter image description here

The broken part 😅

However as you can tell from the following gif, this does not work on small devices with insufficient vertical space (in this case 255px). Actually, as you can tell from the GIF, in 2 of the cases it was very close. The position was correct, only the height was off.

enter image description here

Here the goal is to use the available space, as illustrated in red:

enter image description here

The code 🤓

I have a stack blitz, where you can experiment here.

component

openPopup(element: any) {
    const overlayRef = this.overlay.create({
      hasBackdrop: true,
      positionStrategy: this.overlay.position()
      .flexibleConnectedTo(element)
      .withLockedPosition()
      .withPositions([
        {
          panelClass: 'custom-panel1',
          originX: 'center',
          originY: 'center',
          overlayX: 'start',
          overlayY: 'top',
        },
        {
          panelClass: 'custom-panel2',
          originX: 'center',
          originY: 'center',
          overlayX: 'start',
          overlayY: 'center',
        },
        {
          panelClass: 'custom-panel3',
          originX: 'center',
          originY: 'center',
          overlayX: 'start',
          overlayY: 'bottom',
        },
      ])
      ,
      width: '200px',
      maxHeight: 300,
    });

    const popupComponentPortal = new ComponentPortal(PopupComponent);

    overlayRef.attach(popupComponentPortal);

    overlayRef.backdropClick().subscribe(() => {
      overlayRef.dispose();
    });
  }

global styles

.cdk-overlay-pane {
    max-height: 300px;
}

Notes

I have been thinking about using the global positioning strategy when the height of the viewport is getting small. However, I would prefer to avoid this scenario, as I would love a solution that could tackle any height of the overlay (respecting the max height off-course).

I recommend using the "Open in New Window LIVE" feature of stackbllitz, when testing the stackblitz. Link is here again.

I would be SO grateful, if you could help solve this issue or point me in the right direction 🤷🏾‍♂️

Upvotes: 6

Views: 17882

Answers (2)

DauleDK
DauleDK

Reputation: 3453

I solved the above problem, by implementing a check - after the dialog is opened. This is done with the resize-observer. The code looks like this:

/*
        Read more about the resize observer here: https://developers.google.com/web/updates/2016/10/resizeobserver
        Basicaly, what we do is that we subscribe to size events on the overlay.
        Currently we only get one event, (then we disconnet the resize observer).
        But then we simply calculate if we need to improve the layout.
        */
        const ro = new ResizeObserver(entries => {
            for (const entry of entries) {
                // We get the hight of the element from the the contnetRect, provided by the resize observer
                const height = entry.contentRect.height;

                const { left: x, top: y } = entry.target.getBoundingClientRect();

                const offsetPlusHeight = y + height;

                const pixelsOverflow = offsetPlusHeight - this.viewPortHeight;
                // If y is negative, we are off-screen to the top.
                // If pixelsOverflow is positive, we are off-screen on the bottom
                // In either case, we adopt a new strategy.
                if (y < 0 || pixelsOverflow > 1) {
                    ro.disconnect();
                    overlayRef.updateSize({
                        height: height,
                        maxHeight: config.maxHeight,
                        width: config.width,
                    });
                    // Trust the initial positioning strategy, in terms of the x coordinate

                    // Now determine if we need to throw the overlap, all the way to the top.
                    const delta = this.viewPortHeight - height;
                    const yOffset = delta > 0 ? delta : 0;

                    // Finnaly, we can apply canculate and apply the new position
                    const newPositionStrategy = this.getGlobalPosition(yOffset, x);
                    overlayRef.updatePositionStrategy(newPositionStrategy);
                }
            }
        });

        // Element for which to observe height and width
        ro.observe(overlayRef.overlayElement);

I think the reason I need this extra check, is because what I am actually looking for is a n cdk overlay strategy for items with variable height.

If you are interested in a working solution, I have a working stackblitz here:

enter image description here

Upvotes: 8

Wandrille
Wandrille

Reputation: 6831

For the height, you can replace :

maxHeight: 300,

by

height: window.innerHeight < 300 ? window.innerHeight + 'px' : '300px',

It's just an idea. It's not a good idea to directly use window in your componnet because you can't mock it. Check this article if you want to know how to do it properly.

Upvotes: 0

Related Questions