viewports: Re-engineer breakpoint detection from JavaScript.

This commit is contained in:
Karl Stolley
2025-02-21 14:30:31 -06:00
committed by Tim Abbott
parent f27a69be71
commit 1c74b1635b
6 changed files with 80 additions and 11 deletions

View File

@@ -12,7 +12,6 @@ import render_presence_rows from "../templates/presence_rows.hbs";
import * as blueslip from "./blueslip.ts";
import * as buddy_data from "./buddy_data.ts";
import type {BuddyUserInfo} from "./buddy_data.ts";
import {media_breakpoints_num} from "./css_variables.ts";
import type {Filter} from "./filter.ts";
import * as hash_util from "./hash_util.ts";
import {$t} from "./i18n.ts";
@@ -26,6 +25,7 @@ import {current_user} from "./state_data.ts";
import * as stream_data from "./stream_data.ts";
import type {StreamSubscription} from "./sub_store.ts";
import {INTERACTIVE_HOVER_DELAY} from "./tippyjs.ts";
import * as ui_util from "./ui_util.ts";
import {user_settings} from "./user_settings.ts";
import * as util from "./util.ts";
@@ -174,7 +174,7 @@ export class BuddyList extends BuddyListConf {
e.stopPropagation();
const $elem = $(this);
let placement: "left" | "auto" = "left";
if (window.innerWidth < media_breakpoints_num.md) {
if (ui_util.matches_viewport_state("lt_md_min")) {
// On small devices display tooltips based on available space.
// This will default to "bottom" placement for this tooltip.
placement = "auto";

View File

@@ -13,7 +13,6 @@ import * as buddy_data from "./buddy_data.ts";
import * as compose_actions from "./compose_actions.ts";
import * as compose_reply from "./compose_reply.ts";
import * as compose_state from "./compose_state.ts";
import {media_breakpoints_num} from "./css_variables.ts";
import * as emoji_picker from "./emoji_picker.ts";
import * as hash_util from "./hash_util.ts";
import * as hashchange from "./hashchange.ts";
@@ -484,7 +483,7 @@ export function initialize(): void {
): void {
let placement: tippy.Placement = "left";
let observer: MutationObserver;
if (window.innerWidth < media_breakpoints_num.md) {
if (ui_util.matches_viewport_state("lt_md_min")) {
// On small devices display tooltips based on available space.
// This will default to "bottom" placement for this tooltip.
placement = "auto";

View File

@@ -6,11 +6,11 @@ import $ from "jquery";
import * as tippy from "tippy.js";
import * as blueslip from "./blueslip.ts";
import {media_breakpoints_num} from "./css_variables.ts";
import * as message_viewport from "./message_viewport.ts";
import * as modals from "./modals.ts";
import * as overlays from "./overlays.ts";
import * as popovers from "./popovers.ts";
import * as ui_util from "./ui_util.ts";
import * as util from "./util.ts";
type PopoverName =
@@ -418,7 +418,7 @@ export function toggle_popover_menu(
// popover centered on the screen as an overlay.
let show_as_overlay =
(options?.show_as_overlay_on_mobile === true &&
window.innerWidth <= media_breakpoints_num.md) ||
ui_util.matches_viewport_state("lt_md_min")) ||
options?.show_as_overlay_always === true;
// Show the popover as over if the reference element is hidden.
if (!show_as_overlay && options?.show_as_overlay_if_reference_hidden_at_trigger) {

View File

@@ -7,7 +7,6 @@ import render_right_sidebar from "../templates/right_sidebar.hbs";
import {buddy_list} from "./buddy_list.ts";
import * as channel from "./channel.ts";
import * as compose_ui from "./compose_ui.ts";
import {media_breakpoints_num} from "./css_variables.ts";
import {reorder_left_sidebar_navigation_list} from "./left_sidebar_navigation_area.ts";
import {localstorage} from "./localstorage.ts";
import * as message_lists from "./message_lists.ts";
@@ -56,7 +55,7 @@ export function show_userlist_sidebar(): void {
return;
}
if (window.innerWidth >= media_breakpoints_num.xl) {
if (ui_util.matches_viewport_state("gte_xl_min")) {
$("body").removeClass("hide-right-sidebar");
fix_invite_user_button_flicker();
return;
@@ -146,7 +145,7 @@ export function initialize(): void {
e.preventDefault();
e.stopPropagation();
if (window.innerWidth >= media_breakpoints_num.xl) {
if (ui_util.matches_viewport_state("gte_xl_min")) {
$("body").toggleClass("hide-right-sidebar");
if (!$("body").hasClass("hide-right-sidebar")) {
fix_invite_user_button_flicker();
@@ -172,11 +171,11 @@ export function initialize(): void {
e.preventDefault();
e.stopPropagation();
if (window.innerWidth >= media_breakpoints_num.md) {
if (ui_util.matches_viewport_state("gte_md_min")) {
$("body").toggleClass("hide-left-sidebar");
if (
message_lists.current !== undefined &&
window.innerWidth <= media_breakpoints_num.xl
!ui_util.matches_viewport_state("gte_xl_min")
) {
// We expand the middle column width between md and xl breakpoints when the
// left sidebar is hidden. This can cause the pointer to move out of view.

View File

@@ -258,3 +258,16 @@ export function show_left_sidebar_menu_icon(element: Element): void {
export function hide_left_sidebar_menu_icon(): void {
$(".left_sidebar_menu_icon_visible").removeClass("left_sidebar_menu_icon_visible");
}
export function matches_viewport_state(state_string: string): boolean {
const app_main = document.querySelector(".app-main");
if (app_main instanceof HTMLElement) {
const app_main_after_content = getComputedStyle(app_main, ":after").content ?? "";
/* The .content property includes the quotation marks, so we
strip them before splitting on the empty space. */
const app_main_after_content_array = app_main_after_content.replaceAll('"', "").split(" ");
return app_main_after_content_array.includes(state_string);
}
return false;
}

View File

@@ -495,6 +495,15 @@ body.has-overlay-scrollbar {
height: 100%;
min-height: 100%;
&::after {
/* We use `content` values to determine
viewport state from JavaScript; this
hides the values without disrupting
the document flow. */
position: absolute;
display: none;
}
.column-left .left-sidebar,
.column-right .right-sidebar {
position: fixed;
@@ -1861,6 +1870,55 @@ body:not(.hide-left-sidebar) {
/* End fallback media queries. */
/* Begin viewport state queries. */
/* We sometimes need the state of the viewport in JavaScript;
by setting values on `content:`, we can query instead for
those, independent of whether it's a container or media query
responsible for layout. See for example `sidebar_ui.ts`.
These queries for greater than or equal to should be ordered
smallest to largest. */
/* Adjustments for smaller viewports are critical
regardless of info-density settings, so we
don't include a container-query check here. */
@media (width < $md_min) {
.app-main::after {
content: "lt_md_min";
}
}
@container app (width >= $cq_md_min) {
.app-main::after {
content: "gte_md_min";
}
}
@media (width >= $md_min) {
.without-container-query-support {
.app-main::after {
content: "gte_md_min";
}
}
}
@container app (width >= $cq_xl_min) {
.app-main::after {
content: "gte_md_min gte_xl_min";
}
}
@media (width >= $xl_min) {
.without-container-query-support {
.app-main::after {
content: "gte_md_min gte_xl_min";
}
}
}
/* End viewport state queries. */
@media (height < $short_navbar_cutoff_height) {
.app-main .column-right.expanded .right-sidebar,
.app-main .column-left.expanded .left-sidebar {