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:
Aman Agrawal
2021-05-17 00:36:29 +00:00
committed by Tim Abbott
parent 6b6dcf6ce1
commit c6d74c4b67

View File

@@ -104,7 +104,7 @@ function is_table_focused() {
return current_focus_elem === "table"; 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"); const topic_rows = $("#recent_topics_table table tbody tr");
if (topic_rows.length === 0 || row < 0 || row >= topic_rows.length) { if (topic_rows.length === 0 || row < 0 || row >= topic_rows.length) {
row_focus = 0; row_focus = 0;
@@ -114,39 +114,32 @@ function set_table_focus(row, col) {
} }
const topic_row = topic_rows.eq(row); const topic_row = topic_rows.eq(row);
topic_row.find(".recent_topics_focusable").eq(col).children().trigger("focus"); // We need to allow table to render first before setting focus.
setTimeout(
// Bring the focused element in view in the smoothest () => topic_row.find(".recent_topics_focusable").eq(col).children().trigger("focus"),
// possible way. Using `block: center` is not a 0,
// 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",
});
current_focus_elem = "table"; 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 = { const message = {
stream: topic_row.find(".recent_topic_stream a").text(), stream: topic_row.find(".recent_topic_stream a").text(),
topic: topic_row.find(".recent_topic_name a").text(), topic: topic_row.find(".recent_topic_name a").text(),
}; };
compose_closed_ui.update_reply_recipient_label(message); 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; return true;
} }
@@ -483,7 +476,7 @@ function topic_sort(a, b) {
return -1; 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 scroll_container = $("#recent_topics_table .table_fix_head");
const thead_height = 30; const thead_height = 30;
const under_closed_compose_region_height = 50; 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_top = $(topic_row).offset().top;
const topic_row_bottom = topic_row_top + $(topic_row).height(); const topic_row_bottom = topic_row_top + $(topic_row).height();
// check if topic_row is inside the visible part of scroll container. // Topic is above the visible scroll region.
return topic_row_bottom <= scroll_container_bottom && topic_row_top >= scroll_container_top; 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() { 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"); const topic_rows = $("#recent_topics_table table tbody tr");
if (row_focus > topic_rows.length) { if (row_focus > topic_rows.length) {
// User used a filter which reduced // User used a filter which reduced
// the number of visible rows. // the number of visible rows.
return; return;
} }
let topic_row = topic_rows.eq(row_focus); 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. // Get the element at the center of the table.
const position = document const position = table_wrapper_element.getBoundingClientRect();
.querySelector("#recent_topics_table .table_fix_head")
.getBoundingClientRect();
const topic_center_x = (position.left + position.right) / 2; const topic_center_x = (position.left + position.right) / 2;
const topic_center_y = (position.top + position.bottom) / 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": case "up_arrow":
row_focus -= 1; row_focus -= 1;
} }
set_table_focus(row_focus, col_focus); set_table_focus(row_focus, col_focus, true);
return true; return true;
} }
if (current_focus_elem && input_key !== "escape") { if (current_focus_elem && input_key !== "escape") {