In my minimal example, when the new "typing..." div is added to the dom, the input goes down, hiding behind the android keyboard a little. Here is a gif showing this, watch just above the input when the typing div appears and the input slides down the keyboard:-
Is there anyway to make sure the input stays in its place? I need to make sure the texts appears from top to bottom. I have tried everything, instead of using justify-end, I have tried using margin top auto, flex col reverse, rotate 180 but all of them gave same problem. I have also tried adding the typing.. as a part of the array itself inside the for loop but it worked partially, i.e., when the message is received from partner it works but if user sends message, and partner is typing, it starts giving the same problem..
I am using tailwind but any solution using anything is fine- weather it is plain css or javascript.
Why this happens? Why it does not happen when the divs are added through @for loop but it happens when the div is added with @if condition? It works fine if I make the divs appear from top to bottom (example not using jusifty-end).
Also if there is another way to achieve this type of layout where messages appear from bottom to top but this problem does not happen, please tell. Thank you!
To Reproduce-
We need to open the keyboard when the typing.. indicator is not visible. If we open it when the typing indicator is visible, it does not happen.
Also side question- the submit icon on bottom right of the keyboard appears as search icon because I have added the type of the input as search. But if i change it to text the password management starts appearing on top of input as described in this question. Is there any way to change that icon back to the "submit arrow"?
Here is my code:-
app.component.html
<div class="flex flex-col h-dvh bg-yellow-300">
<header class="fixed top-0 h-10 border w-full border-blue-600">
Banner
</header>
<div class="my-10 h-full flex flex-col-reverse overflow-y-auto">
@if (showElement) {
<div class="bg-yellow-100 text-xs">...typing</div>
}
@for (message of messages(); track $index) {
<div>{{ message }}</div>
}
</div>
<div class="flex bottom-0 fixed border-2 h-10 border-red-500 w-full">
<input type="search" autocomplete="off" class="border-2 border-green-500 grow normal-text">
</div>
</div>
app.component.ts
import { isPlatformBrowser } from '@angular/common';
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID, signal } from '@angular/core';
@Component({
selector: 'app-root',
imports: [],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit, OnDestroy {
showElement = false;
messages = signal<string[]>([]);
intervalId: any;
intervalId2: any;
counter = 1;
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
ngOnInit(): void {
if(isPlatformBrowser(this.platformId)) {
this.intervalId = setInterval(() => {
this.messages.set(['test'+this.counter++, ...this.messages()]);
}, 500);
setInterval(() => {
this.intervalId2 = this.showElement = !this.showElement;
}, 1000); // Toggles every second
}
}
ngOnDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId); // Cleanup to prevent memory leaks
}
if (this.intervalId2) {
clearInterval(this.intervalId2); // Cleanup to prevent memory leaks
}
}
}
app.component.scss
.normal-text {
-webkit-appearance: none;
appearance: none;
-moz-appearance: none;
}
styles.scss
/* You can add global styles to this file, and also import other style files */
@tailwind base;
@tailwind components;
@tailwind utilities;
Update-
Using the solution suggested by @C3roe in comments (if i got it right)- if i scroll up I do scroll down to the bottom of messages, but the typing indicator is still pushing the input down the keyboard. StackBlitz of this scrolling solution example. Demonstration gif:-
Specific Code Updates-
app.component.ts
import { isPlatformBrowser } from '@angular/common';
import { Component, ElementRef, Inject, OnDestroy, OnInit, PLATFORM_ID, Renderer2, signal, ViewChild, viewChild } from '@angular/core';
@Component({
selector: 'app-root',
imports: [],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit, OnDestroy {
// getting ref of the div that contains the messages / typing indicator
@ViewChild('messageContainer') messageContainer: ElementRef | undefined = undefined;
showElement = false;
messages = signal<string[]>([]);
intervalId: any;
intervalId2: any;
counter = 1;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private renderer: Renderer2
) {}
ngOnInit(): void {
if(isPlatformBrowser(this.platformId)) {
this.intervalId = setInterval(() => {
this.messages.set(['test'+this.counter++, ...this.messages()]);
}, 500);
setInterval(() => {
this.intervalId2 = this.showElement = !this.showElement;
if(this.showElement) {
if(this.messageContainer) {
// scrolling the div down
let elem = this.renderer.selectRootElement(this.messageContainer)?.nativeElement;
// = 0 because column is in reverse
elem.scrollTop = 0;
}
}
}, 1000);
}
}
ngOnDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
if (this.intervalId2) {
clearInterval(this.intervalId2);
}
}
}
app.component.html
<div class="flex flex-col h-dvh bg-yellow-300">
<header class="fixed top-0 h-10 border w-full border-blue-600">
Banner
</header>
<!-- SCROLLING THIS DOWN -->
<div class="my-10 h-full flex flex-col-reverse overflow-y-auto" #messageContainer>
@if (showElement) {
<div class="bg-yellow-100 text-xs">...typing</div>
}
@for (message of messages(); track $index) {
<div>{{ message }}</div>
}
</div>
<div class="flex bottom-0 fixed border-2 h-10 border-red-500 w-full">
<input type="search" autocomplete="off" class="border-2 border-green-500 grow normal-text">
</div>
</div>
Update
Add gif of possible size change in chrome remote debugger:-
Quoting from this question on reddit
Chrome 108 changed the default behavior of the on-screen keyboard. It no longer affects the Layout Viewport, therefore dvh units are unchanged, resulting in the behavior seen here.
It seems like the new chrome version changed the default behaviour due to some of their "special reasons".
Adding this tag in the head element of html returns to the default behaviour where the screen resizes when the keyboard is opened:-
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">
More info can be found here.
Old Answer-
While we find the actual reason and answer to this, I found a workaround to make it work. In case it can be helpful for someome, here it is-
We put the typing indicator inside the bottom fixed div just above the input and make sure the we define height for it and input.
We dynamically increase margin on the container of messages, when the typing indicator is visible so that its content is not hidden behind the typing indicator and it give the effect like the typing indicator has pushed it up.
app.component.html
<div class="flex flex-col h-dvh bg-yellow-300">
<header class="fixed top-0 h-10 border w-full border-blue-600">
Banner
</header>
<!-- Adding extra margin below when typing indicator is visible -->
<div class="mt-10 flex-1 flex flex-col-reverse overflow-y-auto mb-10" [class.mb-16]="showElement">
@for (message of messages(); track $index) {
<div>{{ message }}</div>
}
</div>
<!-- Adding the typing indicator here above the search input text and fixed heights -->
<div class="flex flex-col bottom-0 fixed w-full">
@if (showElement) {
<div class="bg-yellow-100 opacity-20 text-xs h-6">...typing</div>
}
<input type="search" autocomplete="off" class="border-2 opacity-20 border-green-500 grow normal-text h-10">
</div>
</div>