zulip/web/src/scroll_util.ts
Shubham Padia 43eebbf9c6 scroll: Set tabIndex to -1 for simplebar content wrapper.
Fixes #30403.
Having tabIndex set to 0 led to keyboard focus being put on
a scrollbar container, which led to users having to tab twice
to skip a container.
This commit also removes instances of tabIndex being set to
-1 programatically for certain cases, since it is -1 by default now.
This commit also removes `outline: none` for simplebar since
that property is not needed anymore because the wrapper is
not focusable anymore.
2024-06-20 10:25:51 -07:00

96 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import $ from "jquery";
import SimpleBar from "simplebar";
// This type is helpful for testing, where we may have a dummy object instead of an actual jquery object.
type JQueryOrZJQuery = {__zjquery?: true} & JQuery;
export function get_content_element($element: JQuery): JQuery {
const element = $element.expectOne()[0]!;
const sb = SimpleBar.instances.get(element);
if (sb) {
return $(sb.getContentElement()!);
}
return $element;
}
export function get_scroll_element($element: JQueryOrZJQuery): JQuery {
// For testing we just return the element itself.
if ($element?.__zjquery) {
return $element;
}
const element = $element.expectOne()[0]!;
const sb = SimpleBar.instances.get(element);
if (sb) {
return $(sb.getScrollElement()!);
} else if ("simplebar" in element.dataset) {
// The SimpleBar mutation observer hasnt processed this element yet.
// Create the SimpleBar early in case we need to add event listeners.
return $(new SimpleBar(element, {tabIndex: -1}).getScrollElement()!);
}
return $element;
}
export function reset_scrollbar($element: JQuery): void {
const element = $element.expectOne()[0]!;
const sb = SimpleBar.instances.get(element);
if (sb) {
sb.getScrollElement()!.scrollTop = 0;
} else {
element.scrollTop = 0;
}
}
export function scroll_delta(opts: {
elem_top: number;
elem_bottom: number;
container_height: number;
}): number {
const elem_top = opts.elem_top;
const container_height = opts.container_height;
const elem_bottom = opts.elem_bottom;
let delta = 0;
if (elem_top < 0) {
delta = Math.max(elem_top, elem_bottom - container_height);
delta = Math.min(0, delta);
} else {
if (elem_bottom > container_height) {
delta = Math.min(elem_top, elem_bottom - container_height);
delta = Math.max(0, delta);
}
}
return delta;
}
export function scroll_element_into_container(
$elem: JQuery,
$container: JQuery,
sticky_header_height = 0,
): void {
// This does the minimum amount of scrolling that is needed to make
// the element visible. It doesn't try to center the element, so
// this will be non-intrusive to users when they already have
// the element visible.
$container = get_scroll_element($container);
const elem_top = $elem.position().top - sticky_header_height;
const elem_bottom = elem_top + ($elem.innerHeight() ?? 0);
const container_height = ($container.height() ?? 0) - sticky_header_height;
const opts = {
elem_top,
elem_bottom,
container_height,
};
const delta = scroll_delta(opts);
if (delta === 0) {
return;
}
$container.scrollTop(($container.scrollTop() ?? 0) + delta);
}