Browse Source

fix(admin-ui): Add scrollbar to dropdowns that go out of the viewport

Michael Bromley 1 year ago
parent
commit
8a78a70fe9

+ 28 - 1
packages/admin-ui/src/lib/core/src/shared/components/dropdown/dropdown-menu.component.ts

@@ -3,6 +3,7 @@ import { TemplatePortal } from '@angular/cdk/portal';
 import {
     AfterViewInit,
     ChangeDetectionStrategy,
+    ChangeDetectorRef,
     Component,
     HostListener,
     Input,
@@ -37,7 +38,7 @@ export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'botto
         <ng-template #menu>
             <div [dir]="direction$ | async">
                 <div class="dropdown open">
-                    <div class="dropdown-menu" [ngClass]="customClasses">
+                    <div class="dropdown-menu" [ngClass]="customClasses" [style.maxHeight.px]="maxHeight">
                         <div
                             class="dropdown-content-wrapper"
                             [cdkTrapFocus]="true"
@@ -62,6 +63,27 @@ export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
     private menuPortal: TemplatePortal;
     private overlayRef: OverlayRef;
     private backdropClickSub: Subscription;
+    protected maxHeight: number | undefined;
+
+    private resizeObserver = new ResizeObserver(entries => {
+        const margin = 12;
+        for (const entry of entries) {
+            const contentWrapper = entry.target.querySelector('.dropdown-content-wrapper');
+            if (contentWrapper) {
+                const { bottom, top } = contentWrapper?.getBoundingClientRect();
+                if (bottom > window.innerHeight - margin) {
+                    // dropdown is going off the bottom of the screen
+                    this.maxHeight = window.innerHeight - top - margin;
+                    this.changeDetector.markForCheck();
+                }
+                if (top < margin) {
+                    // dropdown is going off the top of the screen
+                    this.maxHeight = bottom - margin;
+                    this.changeDetector.markForCheck();
+                }
+            }
+        }
+    });
 
     @HostListener('window:keydown.escape', ['$event'])
     onEscapeKeydown(event: KeyboardEvent) {
@@ -103,6 +125,7 @@ export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
         private viewContainerRef: ViewContainerRef,
         private dropdown: DropdownComponent,
         private localizationService: LocalizationService,
+        private changeDetector: ChangeDetectorRef,
     ) {}
 
     ngOnInit(): void {
@@ -111,8 +134,11 @@ export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
         this.dropdown.onOpenChange(isOpen => {
             if (isOpen) {
                 this.overlayRef.attach(this.menuPortal);
+                this.resizeObserver.observe(this.overlayRef.overlayElement);
             } else {
                 this.overlayRef.detach();
+                this.resizeObserver.unobserve(this.overlayRef.overlayElement);
+                this.maxHeight = undefined;
             }
         });
     }
@@ -124,6 +150,7 @@ export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
             positionStrategy: this.getPositionStrategy(),
             maxHeight: '70vh',
         });
+
         this.menuPortal = new TemplatePortal(this.menuTemplate, this.viewContainerRef);
         this.backdropClickSub = this.overlayRef.backdropClick().subscribe(() => {
             this.dropdown.toggleOpen();