From 870d6ee782257167234f4de312dee7699877fd38 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Mon, 23 Sep 2024 10:29:37 -0700
Subject: [PATCH] Fix: handle overflowing dropdowns on mobile (#7758)

See https://github.com/ng-bootstrap/ng-bootstrap/pull/4760
---
 .../dates-dropdown.component.html             |  2 +-
 .../dates-dropdown.component.ts               |  3 +++
 .../filterable-dropdown.component.html        |  2 +-
 .../filterable-dropdown.component.ts          |  3 +++
 src-ui/src/app/utils/popper-options.spec.ts   | 24 +++++++++++++++++++
 src-ui/src/app/utils/popper-options.ts        | 24 +++++++++++++++++++
 6 files changed, 56 insertions(+), 2 deletions(-)
 create mode 100644 src-ui/src/app/utils/popper-options.spec.ts
 create mode 100644 src-ui/src/app/utils/popper-options.ts

diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html
index 8991363d2..b9528805b 100644
--- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html
+++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.html
@@ -1,4 +1,4 @@
-<div class="btn-group w-100" ngbDropdown role="group">
+<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions">
   <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateBefore || createdDateAfter ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
     <i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs>
     <div class="d-none d-sm-inline">&nbsp;{{title}}</div>
diff --git a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts
index 966e9640a..21b39f0cb 100644
--- a/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts
+++ b/src-ui/src/app/components/common/dates-dropdown/dates-dropdown.component.ts
@@ -11,6 +11,7 @@ import { Subject, Subscription } from 'rxjs'
 import { debounceTime } from 'rxjs/operators'
 import { SettingsService } from 'src/app/services/settings.service'
 import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
+import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
 
 export interface DateSelection {
   createdBefore?: string
@@ -35,6 +36,8 @@ export enum RelativeDate {
   providers: [{ provide: NgbDateAdapter, useClass: ISODateAdapter }],
 })
 export class DatesDropdownComponent implements OnInit, OnDestroy {
+  public popperOptions = popperOptionsReenablePreventOverflow
+
   constructor(settings: SettingsService) {
     this.datePlaceHolder = settings.getLocalizedDateInputFormat()
   }
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
index 9e9a73124..413ed8d3b 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
@@ -1,4 +1,4 @@
-<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown" (keydown)="listKeyDown($event)">
+<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown" (keydown)="listKeyDown($event)" [popperOptions]="popperOptions">
   <button class="btn btn-sm" id="dropdown_{{name}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
     <i-bs name="{{icon}}"></i-bs>
     <div class="d-none d-sm-inline">&nbsp;{{title}}</div>
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
index 7830e3909..5e39b1d2d 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
@@ -16,6 +16,7 @@ import { Subject, filter, take, takeUntil } from 'rxjs'
 import { SelectionDataItem } from 'src/app/services/rest/document.service'
 import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
 import { HotKeyService } from 'src/app/services/hot-key.service'
+import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
 
 export interface ChangedItems {
   itemsToAdd: MatchingModel[]
@@ -330,6 +331,8 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
   @ViewChild('dropdown') dropdown: NgbDropdown
   @ViewChild('buttonItems') buttonItems: ElementRef
 
+  public popperOptions = popperOptionsReenablePreventOverflow
+
   filterText: string
 
   @Input()
diff --git a/src-ui/src/app/utils/popper-options.spec.ts b/src-ui/src/app/utils/popper-options.spec.ts
new file mode 100644
index 000000000..9d7671cf6
--- /dev/null
+++ b/src-ui/src/app/utils/popper-options.spec.ts
@@ -0,0 +1,24 @@
+import { popperOptionsReenablePreventOverflow } from './popper-options'
+import { Options } from '@popperjs/core'
+
+describe('popperOptionsReenablePreventOverflow', () => {
+  it('should return the config without the empty fun preventOverflow, add padding to other', () => {
+    const config: Partial<Options> = {
+      modifiers: [
+        { name: 'preventOverflow', fn: function () {} },
+        {
+          name: 'preventOverflow',
+          fn: function () {
+            return
+          },
+        },
+      ],
+    }
+
+    const result = popperOptionsReenablePreventOverflow(config)
+
+    expect(result.modifiers.length).toBe(1)
+    expect(result.modifiers[0].name).toBe('preventOverflow')
+    expect(result.modifiers[0].options).toEqual({ padding: 10 })
+  })
+})
diff --git a/src-ui/src/app/utils/popper-options.ts b/src-ui/src/app/utils/popper-options.ts
new file mode 100644
index 000000000..71ac715ce
--- /dev/null
+++ b/src-ui/src/app/utils/popper-options.ts
@@ -0,0 +1,24 @@
+import { Options } from '@popperjs/core'
+
+export function popperOptionsReenablePreventOverflow(
+  config: Partial<Options>
+): Partial<Options> {
+  const preventOverflowModifier = config.modifiers?.find(
+    (m) => m.name === 'preventOverflow' && m.fn?.length === 0
+  )
+  if (preventOverflowModifier) {
+    config.modifiers.splice(
+      config.modifiers.indexOf(preventOverflowModifier),
+      1
+    )
+  }
+  const ogPreventOverflowModifier = config.modifiers.find(
+    (m) => m.name === 'preventOverflow'
+  )
+  if (ogPreventOverflowModifier) {
+    ogPreventOverflowModifier.options = {
+      padding: 10,
+    }
+  }
+  return config
+}