mirror of
https://github.com/zulip/zulip.git
synced 2025-11-09 16:37:23 +00:00
recent_topics: Separate behaviour for scrolling and hotkeys.
When user is scrolling, we simply keep the center element in focus. When user is using hotkeys, we keep the focused element in center. When user is using keyboard, we need to always keep the "focused" topic in visible scrolling area. We determine if the topic row is above or below the visible area and scroll half_height_of_visible_area so that the selected topic is visible. This gives a nice navigation experience for both the views. Reduced height of recent topics table to account for compose box so that focused element is not below compose box.
This commit is contained in:
@@ -104,7 +104,7 @@ function is_table_focused() {
|
||||
return current_focus_elem === "table";
|
||||
}
|
||||
|
||||
function set_table_focus(row, col) {
|
||||
function set_table_focus(row, col, using_keyboard) {
|
||||
const topic_rows = $("#recent_topics_table table tbody tr");
|
||||
if (topic_rows.length === 0 || row < 0 || row >= topic_rows.length) {
|
||||
row_focus = 0;
|
||||
@@ -114,39 +114,32 @@ function set_table_focus(row, col) {
|
||||
}
|
||||
|
||||
const topic_row = topic_rows.eq(row);
|
||||
topic_row.find(".recent_topics_focusable").eq(col).children().trigger("focus");
|
||||
|
||||
// Bring the focused element in view in the smoothest
|
||||
// possible way. Using `block: center` is not a
|
||||
// smooth scrolling experience.
|
||||
// Using {block: "nearest"}, the element:
|
||||
// * is aligned at the top of its ancestor if you're currently below it.
|
||||
// * is aligned at the bottom of its ancestor if you're currently above it.
|
||||
// * stays put, if it's already in view
|
||||
// NOTE: Although, according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#browser_compatibility
|
||||
// `scrollIntoView` is not fully supported on Safari,
|
||||
// it works as intended on Safari v14.0.3 on macOS Big Sur.
|
||||
topic_row.get()[0].scrollIntoView({
|
||||
block: "nearest",
|
||||
});
|
||||
|
||||
// We need to allow table to render first before setting focus.
|
||||
setTimeout(
|
||||
() => topic_row.find(".recent_topics_focusable").eq(col).children().trigger("focus"),
|
||||
0,
|
||||
);
|
||||
current_focus_elem = "table";
|
||||
|
||||
if (using_keyboard) {
|
||||
const scroll_element = document.querySelector(
|
||||
"#recent_topics_table .table_fix_head .simplebar-content-wrapper",
|
||||
);
|
||||
const half_height_of_visible_area = scroll_element.offsetHeight / 2;
|
||||
const topic_offset = topic_offset_to_visible_area(topic_row);
|
||||
|
||||
if (topic_offset === "above") {
|
||||
scroll_element.scrollBy({top: -1 * half_height_of_visible_area});
|
||||
} else if (topic_offset === "below") {
|
||||
scroll_element.scrollBy({top: half_height_of_visible_area});
|
||||
}
|
||||
}
|
||||
|
||||
const message = {
|
||||
stream: topic_row.find(".recent_topic_stream a").text(),
|
||||
topic: topic_row.find(".recent_topic_name a").text(),
|
||||
};
|
||||
compose_closed_ui.update_reply_recipient_label(message);
|
||||
|
||||
// focused topic can be under table `thead`
|
||||
// or under compose, so, to avoid that
|
||||
// from happening, we bring the element to center.
|
||||
if (!is_topic_visible_to_user(topic_row)) {
|
||||
topic_row.get()[0].scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -483,7 +476,7 @@ function topic_sort(a, b) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function is_topic_visible_to_user(topic_row) {
|
||||
function topic_offset_to_visible_area(topic_row) {
|
||||
const scroll_container = $("#recent_topics_table .table_fix_head");
|
||||
const thead_height = 30;
|
||||
const under_closed_compose_region_height = 50;
|
||||
@@ -495,23 +488,32 @@ function is_topic_visible_to_user(topic_row) {
|
||||
const topic_row_top = $(topic_row).offset().top;
|
||||
const topic_row_bottom = topic_row_top + $(topic_row).height();
|
||||
|
||||
// check if topic_row is inside the visible part of scroll container.
|
||||
return topic_row_bottom <= scroll_container_bottom && topic_row_top >= scroll_container_top;
|
||||
// Topic is above the visible scroll region.
|
||||
if (topic_row_top < scroll_container_top) {
|
||||
return "above";
|
||||
// Topic is below the visible scroll region.
|
||||
} else if (topic_row_bottom > scroll_container_bottom) {
|
||||
return "below";
|
||||
}
|
||||
|
||||
// Topic is visible
|
||||
return "visible";
|
||||
}
|
||||
|
||||
function set_focus_to_element_in_center() {
|
||||
const table_wrapper_element = document.querySelector("#recent_topics_table .table_fix_head");
|
||||
const topic_rows = $("#recent_topics_table table tbody tr");
|
||||
|
||||
if (row_focus > topic_rows.length) {
|
||||
// User used a filter which reduced
|
||||
// the number of visible rows.
|
||||
return;
|
||||
}
|
||||
let topic_row = topic_rows.eq(row_focus);
|
||||
if (!is_topic_visible_to_user(topic_row)) {
|
||||
const topic_offset = topic_offset_to_visible_area(topic_row);
|
||||
if (topic_offset !== "visible") {
|
||||
// Get the element at the center of the table.
|
||||
const position = document
|
||||
.querySelector("#recent_topics_table .table_fix_head")
|
||||
.getBoundingClientRect();
|
||||
const position = table_wrapper_element.getBoundingClientRect();
|
||||
const topic_center_x = (position.left + position.right) / 2;
|
||||
const topic_center_y = (position.top + position.bottom) / 2;
|
||||
|
||||
@@ -817,7 +819,7 @@ export function change_focused_element($elt, input_key) {
|
||||
case "up_arrow":
|
||||
row_focus -= 1;
|
||||
}
|
||||
set_table_focus(row_focus, col_focus);
|
||||
set_table_focus(row_focus, col_focus, true);
|
||||
return true;
|
||||
}
|
||||
if (current_focus_elem && input_key !== "escape") {
|
||||
|
||||
Reference in New Issue
Block a user