DEV: improves keyboard sizing (#26372)

This commit is making the following changes:

- replaces `mobile-keyboard` initializer and `chat-vh` with a new template-less component: `d-vh`
- ensures body scroll lock is released when page/tab focus changes
- correctly locks body on chat channels and chat threads when composer is focused
- removes `bodyScrollFix` as we now use body scroll lock
- `onViewportResize` has been debounced to ensure it's not a bad performance vector
- adds a reverse option do body scroll lock, this is made to support reversed scroll areas (like chat channels and threads)

---------

Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
This commit is contained in:
Joffrey JAFFEUX
2024-03-27 08:50:32 +01:00
committed by GitHub
parent 9baa820d53
commit f9eae75972
16 changed files with 221 additions and 199 deletions

View File

@@ -27,7 +27,6 @@ import {
PAST,
READ_INTERVAL_MS,
} from "discourse/plugins/chat/discourse/lib/chat-constants";
import { bodyScrollFix } from "discourse/plugins/chat/discourse/lib/chat-ios-hacks";
import ChatMessagesLoader from "discourse/plugins/chat/discourse/lib/chat-messages-loader";
import {
checkMessageBottomVisibility,
@@ -184,7 +183,6 @@ export default class ChatChannel extends Component {
onPresenceChangeCallback(present) {
if (present) {
this.debouncedUpdateLastReadMessage();
bodyScrollFix({ delayed: true });
}
}
@@ -463,7 +461,6 @@ export default class ChatChannel extends Component {
return;
}
bodyScrollFix();
DatesSeparatorsPositioner.apply(this.scrollable);
this.needsArrow =
@@ -765,6 +762,7 @@ export default class ChatChannel extends Component {
@channel={{@channel}}
@uploadDropZone={{this.uploadDropZone}}
@onSendMessage={{this.onSendMessage}}
@scrollable={{this.scrollable}}
/>
{{/if}}
{{/if}}

View File

@@ -141,6 +141,7 @@ export default class ChatComposer extends Component {
@action
setup() {
this.composer.scrollable = this.args.scrollable;
this.appEvents.on("chat:modify-selection", this, "modifySelection");
this.appEvents.on(
"chat:open-insert-link-modal",

View File

@@ -16,6 +16,7 @@ import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import optionalService from "discourse/lib/optional-service";
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
import isZoomed from "discourse/lib/zoom-check";
import discourseDebounce from "discourse-common/lib/debounce";
import discourseLater from "discourse-common/lib/later";
import { bind } from "discourse-common/utils/decorators";
@@ -30,7 +31,6 @@ import ChatMessageSeparator from "discourse/plugins/chat/discourse/components/ch
import ChatMessageText from "discourse/plugins/chat/discourse/components/chat-message-text";
import ChatMessageThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
import isZoomed from "discourse/plugins/chat/discourse/lib/zoom-check";
import ChatOnLongPress from "discourse/plugins/chat/discourse/modifiers/chat/on-long-press";
let _chatMessageDecorators = [];

View File

@@ -19,10 +19,7 @@ import {
PAST,
READ_INTERVAL_MS,
} from "discourse/plugins/chat/discourse/lib/chat-constants";
import {
bodyScrollFix,
stackingContextFix,
} from "discourse/plugins/chat/discourse/lib/chat-ios-hacks";
import { stackingContextFix } from "discourse/plugins/chat/discourse/lib/chat-ios-hacks";
import ChatMessagesLoader from "discourse/plugins/chat/discourse/lib/chat-messages-loader";
import DatesSeparatorsPositioner from "discourse/plugins/chat/discourse/lib/dates-separators-positioner";
import { extractCurrentTopicInfo } from "discourse/plugins/chat/discourse/lib/extract-current-topic-info";
@@ -119,7 +116,6 @@ export default class ChatThread extends Component {
return;
}
bodyScrollFix();
DatesSeparatorsPositioner.apply(this.scrollable);
this.needsArrow =
@@ -578,6 +574,7 @@ export default class ChatThread extends Component {
@thread={{@thread}}
@onSendMessage={{this.onSendMessage}}
@uploadDropZone={{this.uploadDropZone}}
@scrollable={{this.scrollable}}
/>
{{/if}}

View File

@@ -1,71 +0,0 @@
import Component from "@ember/component";
import { service } from "@ember/service";
import { bind } from "discourse-common/utils/decorators";
import isZoomed from "discourse/plugins/chat/discourse/lib/zoom-check";
const CSS_VAR = "--chat-vh";
let lastVH;
export default class ChatVh extends Component {
@service capabilities;
tagName = "";
didInsertElement() {
super.didInsertElement(...arguments);
if ("virtualKeyboard" in navigator) {
navigator.virtualKeyboard.overlaysContent = true;
navigator.virtualKeyboard.addEventListener("geometrychange", this.setVH);
}
this.activeWindow = window.visualViewport || window;
this.activeWindow.addEventListener("resize", this.setVH);
this.setVH();
}
willDestroyElement() {
super.willDestroyElement(...arguments);
this.activeWindow?.removeEventListener("resize", this.setVH);
lastVH = null;
if ("virtualKeyboard" in navigator) {
navigator.virtualKeyboard.removeEventListener(
"geometrychange",
this.setVH
);
}
}
@bind
setVH() {
if (isZoomed()) {
return;
}
let height;
if ("virtualKeyboard" in navigator) {
height =
window.visualViewport.height -
navigator.virtualKeyboard.boundingRect.height;
} else {
height = this.activeWindow?.height || window.innerHeight;
}
const vh = height * 0.01;
if (lastVH === vh) {
return;
}
lastVH = vh;
document.documentElement.style.setProperty(CSS_VAR, `${vh}px`);
}
#blurActiveElement() {
if (document.activeElement?.blur) {
document.activeElement.blur();
}
}
}

View File

@@ -1,7 +1,6 @@
import { next, schedule } from "@ember/runloop";
import { capabilities } from "discourse/services/capabilities";
import discourseLater from "discourse-common/lib/later";
import isZoomed from "discourse/plugins/chat/discourse/lib/zoom-check";
// since -webkit-overflow-scrolling: touch can't be used anymore to disable momentum scrolling
// we use different hacks to work around this
@@ -33,21 +32,3 @@ export function stackingContextFix(scrollable, callback) {
});
}
}
export function bodyScrollFix(options = {}) {
// when keyboard is visible this will ensure body
// doesnt scroll out of viewport
if (
capabilities.isIOS &&
document.documentElement.classList.contains("keyboard-visible") &&
!isZoomed()
) {
if (options.delayed) {
setTimeout(() => {
document.documentElement.scrollTo(0, 0);
}, 200);
} else {
document.documentElement.scrollTo(0, 0);
}
}
}

View File

@@ -1,10 +0,0 @@
import { isTesting } from "discourse-common/config/environment";
// return true when the browser viewport is zoomed
export default function isZoomed() {
return (
!isTesting() &&
visualViewport?.scale !== 1 &&
document.documentElement.clientWidth / window.innerWidth !== 1
);
}

View File

@@ -1,6 +1,8 @@
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { schedule } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { disableBodyScroll } from "discourse/lib/body-scroll-lock";
export default class ChatChannelComposer extends Service {
@service chat;
@@ -9,12 +11,31 @@ export default class ChatChannelComposer extends Service {
@service router;
@service("chat-thread-composer") threadComposer;
@service loadingSlider;
@service capabilities;
@service appEvents;
@tracked textarea;
@tracked scrollable;
init() {
super.init(...arguments);
this.appEvents.on("discourse:focus-changed", this, this.blur);
}
willDestroy() {
super.willDestroy(...arguments);
this.appEvents.off("discourse:focus-changed", this, this.blur);
}
@action
focus(options = {}) {
this.textarea?.focus(options);
schedule("afterRender", () => {
if (this.capabilities.isIOS && !this.capabilities.isIpadOS) {
disableBodyScroll(this.scrollable, { reverse: true });
}
});
}
@action

View File

@@ -1,15 +1,36 @@
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { schedule } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { disableBodyScroll } from "discourse/lib/body-scroll-lock";
export default class ChatThreadComposer extends Service {
@service chat;
@service capabilities;
@service appEvents;
@tracked textarea;
@tracked scrollable;
init() {
super.init(...arguments);
this.appEvents.on("discourse:focus-changed", this, this.blur);
}
willDestroy() {
super.willDestroy(...arguments);
this.appEvents.off("discourse:focus-changed", this, this.blur);
}
@action
focus(options = {}) {
this.textarea?.focus(options);
schedule("afterRender", () => {
if (this.capabilities.isIOS && !this.capabilities.isIpadOS) {
disableBodyScroll(this.scrollable, { reverse: true });
}
});
}
@action

View File

@@ -1,7 +1,5 @@
<div id="chat-progress-bar-container"></div>
<ChatVh />
{{#if this.chat.sidebarActive}}
<div class="full-page-chat teams-sidebar-on">
{{outlet}}

View File

@@ -2,7 +2,7 @@
// desktop and mobile
// -1px is for the bottom border of the chat navbar
$base-height: calc(
var(--chat-vh, 1vh) * 100 - var(--header-offset, 0px) - 1px - $inset
var(--composer-vh, 1vh) * 100 - var(--header-offset, 0px) - 1px - $inset
);
height: calc($base-height - var(--composer-height, 0px));