Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-scroll to latest message #18

Merged
merged 6 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/vaadin-message-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import { microTask } from '@polymer/polymer/lib/utils/async.js';
import '@polymer/polymer/lib/elements/dom-repeat.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
Expand Down Expand Up @@ -59,7 +60,8 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) {
type: Array,
value: function () {
return [];
}
},
observer: '_itemsChanged'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single property observer only handles the cases when the whole array is reset: messageList.items = []
But it does not cover updating the items with Polymer magic methods, e.g. messageList.push('items', {}).

Please consider using a multi-property observer instead, see vaadin-menu-bar for an example.

One downside of multi-propery observers is that you will not get an old value though.

Copy link
Member Author

@heruan heruan Feb 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @web-padawan for the suggestion: we discussed this and decided to observe only the replacement of the whole array (also considering the future transition to Lit).

}
};
}
Expand Down Expand Up @@ -99,6 +101,21 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) {
this.setAttribute('tabindex', '0');
}

_itemsChanged(newVal, oldVal) {
if (
(!oldVal || newVal.length > oldVal.length) && // there are new items
this.scrollHeight < this.clientHeight + this.scrollTop + 50 // bottom of list
) {
microTask.run(() => this._scrollToLastMessage());
}
}

_scrollToLastMessage() {
if (this.items.length > 0) {
this.scrollTop = this.scrollHeight - this.clientHeight;
}
}

static get is() {
return 'vaadin-message-list';
}
Expand Down
60 changes: 59 additions & 1 deletion test/message-list.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ describe('message-list', () => {
userImg: '/test/visual/avatars/avatar.jpg',
userColorIndex: 1
},
{
text: 'A message in the stream of messages',
time: '9:35 AM',
user: {
name: 'Joan Doe',
abbr: 'JD',
img: '/test/visual/avatars/avatar.jpg',
colorIndex: 1
}
},
{
text: 'A message in the stream of messages',
time: '9:36 AM',
user: {
name: 'Joan Doe',
abbr: 'JD',
img: '/test/visual/avatars/avatar.jpg',
colorIndex: 1
}
},
{
text: 'Call upon the times of glory',
time: '2:34 PM',
Expand All @@ -55,7 +75,7 @@ describe('message-list', () => {

it('message list should have two messages', () => {
const items = messageList.shadowRoot.querySelectorAll('vaadin-message');
expect(items.length).to.equal(2);
expect(items.length).to.equal(4);
});

it('message properties should be correctly set', () => {
Expand All @@ -74,5 +94,43 @@ describe('message-list', () => {
messageList.scrollBy(0, 1000);
expect(messageList.scrollTop).to.be.at.least(1);
});

it('message list should scroll to bottom on new messages', async () => {
messageList.style.height = '100px';
messageList.scrollBy(0, 1000);
const scrollTopBeforeMessage = messageList.scrollTop;
messageList.items = [
...messageList.items,
{
text: 'A new message arrives!',
time: '2:35 PM',
user: {
name: 'Steve Mops',
abbr: 'SM',
colorIndex: 2
}
}
];
await nextRender(messageList);
expect(messageList.scrollTop).to.be.at.least(scrollTopBeforeMessage + 1);
});

it('message list should not scroll if not at the bottom', async () => {
messageList.style.height = '100px';
messageList.items = [
...messageList.items,
{
text: 'A new message arrives!',
time: '2:35 PM',
user: {
name: 'Steve Mops',
abbr: 'SM',
colorIndex: 2
}
}
];
await nextRender(messageList);
expect(messageList.scrollTop).to.be.equal(0);
});
});
});