dropdown: Fix dropdown partially hidden at small heights.

We generalize the approach used for compose box to apply it for
all the dropdowns.
This commit is contained in:
Aman Agrawal
2024-07-15 12:59:48 +00:00
committed by Tim Abbott
parent 51eebf84e7
commit b91b643448
12 changed files with 64 additions and 58 deletions

View File

@@ -67,6 +67,8 @@ type DropdownWidgetOptions = {
hide_search_box?: boolean;
// Disable the widget for spectators.
disable_for_spectators?: boolean;
dropdown_input_visible_selector?: string;
prefer_top_start_placement?: boolean;
};
export class DropdownWidget {
@@ -96,6 +98,8 @@ export class DropdownWidget {
text_if_current_value_not_in_options: string;
hide_search_box: boolean;
disable_for_spectators: boolean;
dropdown_input_visible_selector: string;
prefer_top_start_placement: boolean;
constructor(options: DropdownWidgetOptions) {
this.widget_name = options.widget_name;
@@ -123,6 +127,9 @@ export class DropdownWidget {
options.text_if_current_value_not_in_options ?? "";
this.hide_search_box = options.hide_search_box ?? false;
this.disable_for_spectators = options.disable_for_spectators ?? false;
this.dropdown_input_visible_selector =
options.dropdown_input_visible_selector ?? this.widget_selector;
this.prefer_top_start_placement = options.prefer_top_start_placement ?? false;
}
init(): void {
@@ -159,6 +166,57 @@ export class DropdownWidget {
}
}
adjust_dropdown_position_post_list_render(tippy_instance: tippy.Instance): void {
let top_offset = 0;
let left_offset = 0;
// Use offset if provided by the widget callers.
if (typeof this.tippy_props?.offset === "object" && this.tippy_props?.offset.length === 2) {
left_offset = this.tippy_props.offset[0];
top_offset = this.tippy_props.offset[1];
}
const window_height = window.innerHeight;
let dropdown_search_box_and_padding_height = 50;
if (this.hide_search_box) {
dropdown_search_box_and_padding_height = 0;
}
const dropdown_input_props = $(this.dropdown_input_visible_selector).get_offset_to_window();
const dropdown_input_top = dropdown_input_props.top;
// Pixels above the dropdown input.
const top_space = dropdown_input_top - top_offset - dropdown_search_box_and_padding_height;
// Pixels below the top of dropdown input.
const bottom_space =
window_height - dropdown_input_top - dropdown_search_box_and_padding_height;
// Show dropdown at bottom by default if we `DEFAULT_DROPDOWN_HEIGHT`
// space available unless the dropdown caller prefers to show at top.
// If we don't have `DEFAULT_DROPDOWN_HEIGHT` space available above
// or below the dropdown input, show the dropdown at maximum space.
let placement: tippy.Placement = "top-start";
let height: number = Math.min(DEFAULT_DROPDOWN_HEIGHT, Math.max(top_space, bottom_space));
if (this.prefer_top_start_placement && top_space > DEFAULT_DROPDOWN_HEIGHT) {
height = DEFAULT_DROPDOWN_HEIGHT;
} else if (!this.prefer_top_start_placement && bottom_space > DEFAULT_DROPDOWN_HEIGHT) {
placement = "bottom-start";
height = DEFAULT_DROPDOWN_HEIGHT;
// Use the provided offset if we have enough space. Otherwise,
// we overlap the top of dropdown with top of dropdown input.
if (this.tippy_props?.offset === undefined) {
top_offset = -1 * dropdown_input_props.height;
}
} else if (bottom_space > top_space) {
placement = "bottom-start";
top_offset = -1 * dropdown_input_props.height;
}
const offset: [number, number] = [left_offset, top_offset];
tippy_instance.setProps({placement, offset});
const $popper = $(tippy_instance.popper);
$popper.find(".dropdown-list-wrapper").css("max-height", height + "px");
}
show_empty_if_no_items($popper: JQuery): void {
assert(this.list_widget !== undefined);
const list_items = this.list_widget.get_current_list();
@@ -361,6 +419,7 @@ export class DropdownWidget {
}, 0);
this.on_show_callback(instance);
this.adjust_dropdown_position_post_list_render(instance);
},
onMount: (instance: tippy.Instance) => {
this.show_empty_if_no_items($(instance.popper));