From d1050376e16eccf88c0fedc05b9f1e68f61c7f6d Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Tue, 7 May 2024 15:15:50 +0530 Subject: [PATCH] bootstrap_typeahead: Fix compose typeahead overflowing when expanded. When compose box is expanded, typeahead overlfows the top of the window. We move the typeahead to the bottom of the screen and let preventOverflow shift it into the visible area. --- web/src/bootstrap_typeahead.ts | 21 ++++++++++++ web/src/compose_ui.ts | 10 ++++++ web/src/composebox_typeahead.js | 45 ++++++++++++++------------ web/tests/composebox_typeahead.test.js | 1 + 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/web/src/bootstrap_typeahead.ts b/web/src/bootstrap_typeahead.ts index d99b73ecde..e3da16d096 100644 --- a/web/src/bootstrap_typeahead.ts +++ b/web/src/bootstrap_typeahead.ts @@ -346,6 +346,27 @@ export class Typeahead { maxWidth: "none", theme: "popover-menu", placement: this.dropup ? "top-start" : "bottom-start", + popperOptions: { + modifiers: [ + { + // This will only work if there is enough space on the fallback placement, otherwise + // `preventOverflow` will be used to position it in the visible space. + name: "flip", + options: { + fallbackPlacements: ["top-start", "bottom-start"], + }, + }, + { + name: "preventOverflow", + options: { + // This seems required to prevent overflow, maybe because our placements are + // not the usual top, bottom, left, right. + // https://popper.js.org/docs/v2/modifiers/prevent-overflow/#altaxis + altAxis: true, + }, + }, + ], + }, interactive: true, appendTo: () => document.body, showOnCreate: true, diff --git a/web/src/compose_ui.ts b/web/src/compose_ui.ts index 7da357ec6b..64e4cf359e 100644 --- a/web/src/compose_ui.ts +++ b/web/src/compose_ui.ts @@ -10,6 +10,7 @@ import { wrapFieldSelection, } from "text-field-edit"; +import type {Typeahead} from "./bootstrap_typeahead"; import * as bulleted_numbered_list_util from "./bulleted_numbered_list_util"; import * as common from "./common"; import {$t} from "./i18n"; @@ -58,8 +59,13 @@ type SelectedLinesSections = { export let compose_spinner_visible = false; export let shift_pressed = false; // true or false export let code_formatting_button_triggered = false; // true or false +export let compose_textarea_typeahead: Typeahead | undefined; let full_size_status = false; // true or false +export function set_compose_textarea_typeahead(typeahead: Typeahead): void { + compose_textarea_typeahead = typeahead; +} + export function set_code_formatting_button_triggered(value: boolean): void { code_formatting_button_triggered = value; } @@ -67,6 +73,10 @@ export function set_code_formatting_button_triggered(value: boolean): void { // Some functions to handle the full size status explicitly export function set_full_size(is_full: boolean): void { full_size_status = is_full; + // Show typeahead at bottom of textarea on compose full size. + if (compose_textarea_typeahead) { + compose_textarea_typeahead.dropup = !is_full; + } } export function is_full_size(): boolean { diff --git a/web/src/composebox_typeahead.js b/web/src/composebox_typeahead.js index c554c5884b..9e53718c2a 100644 --- a/web/src/composebox_typeahead.js +++ b/web/src/composebox_typeahead.js @@ -1157,27 +1157,30 @@ export function initialize_compose_typeahead(selector) { $element: $(selector), type: "textarea", }; - new Typeahead(bootstrap_typeahead_input, { - items: max_num_items, - dropup: true, - // Performance note: We have trivial matcher/sorters to do - // matching and sorting inside the `source` field to avoid - // O(n) behavior in the number of users in the organization - // inside the typeahead library. - source: get_sorted_filtered_items, - highlighter_html: content_highlighter_html, - matcher() { - return true; - }, - sorter(items) { - return items; - }, - updater: content_typeahead_selected, - stopAdvance: true, // Do not advance to the next field on a Tab or Enter - automated: compose_automated_selection, - trigger_selection: compose_trigger_selection, - header_html: get_header_html, - }); + + compose_ui.set_compose_textarea_typeahead( + new Typeahead(bootstrap_typeahead_input, { + items: max_num_items, + dropup: true, + // Performance note: We have trivial matcher/sorters to do + // matching and sorting inside the `source` field to avoid + // O(n) behavior in the number of users in the organization + // inside the typeahead library. + source: get_sorted_filtered_items, + highlighter_html: content_highlighter_html, + matcher() { + return true; + }, + sorter(items) { + return items; + }, + updater: content_typeahead_selected, + stopAdvance: true, // Do not advance to the next field on a Tab or Enter + automated: compose_automated_selection, + trigger_selection: compose_trigger_selection, + header_html: get_header_html, + }), + ); } export function initialize({on_enter_send}) { diff --git a/web/tests/composebox_typeahead.test.js b/web/tests/composebox_typeahead.test.js index 065d144a32..a7fe03d553 100644 --- a/web/tests/composebox_typeahead.test.js +++ b/web/tests/composebox_typeahead.test.js @@ -17,6 +17,7 @@ const compose_ui = mock_esm("../src/compose_ui", { }, cursor_inside_code_block: () => false, set_code_formatting_button_triggered: noop, + set_compose_textarea_typeahead: noop, }); const compose_validate = mock_esm("../src/compose_validate", { validate_message_length: () => true,