From e8929cb820b2cfa018d06121e7bd403c31e530ec Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Mon, 8 Feb 2021 11:52:11 +0100 Subject: [PATCH 1/4] Add auto-scroll to latest message --- src/vaadin-message-list.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vaadin-message-list.js b/src/vaadin-message-list.js index c2434c6..49e22d2 100644 --- a/src/vaadin-message-list.js +++ b/src/vaadin-message-list.js @@ -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'; @@ -53,7 +54,8 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) { type: Array, value: function () { return []; - } + }, + observer: '_itemsChanged' } }; } @@ -81,6 +83,19 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) { this.setAttribute('role', 'list'); } + _itemsChanged(newVal, oldVal) { + if ( + (!oldVal || newVal.length > oldVal.length) && // there are new items + this.offsetTop + this.offsetHeight < window.pageYOffset + window.innerHeight // bottom of window + ) { + microTask.run(() => this._scrollToLastMessage()); + } + } + + _scrollToLastMessage() { + this.shadowRoot.querySelectorAll('vaadin-message')[this.items.length - 1].scrollIntoView(); + } + static get is() { return 'vaadin-message-list'; } From aebad3ca4a7b21c2f796625037fd43fe76cb7aaf Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Mon, 8 Feb 2021 12:01:15 +0100 Subject: [PATCH 2/4] Scroll only if there is at least one message --- src/vaadin-message-list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vaadin-message-list.js b/src/vaadin-message-list.js index 49e22d2..9fcf183 100644 --- a/src/vaadin-message-list.js +++ b/src/vaadin-message-list.js @@ -93,7 +93,9 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) { } _scrollToLastMessage() { - this.shadowRoot.querySelectorAll('vaadin-message')[this.items.length - 1].scrollIntoView(); + if (this.items.length > 0) { + this.shadowRoot.querySelectorAll('vaadin-message')[this.items.length - 1].scrollIntoView(); + } } static get is() { From ea1aaca64af77aa4855f5a07d38b11aeb58fdde9 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Mon, 8 Feb 2021 13:35:40 +0100 Subject: [PATCH 3/4] Scroll with scrollTop --- src/vaadin-message-list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vaadin-message-list.js b/src/vaadin-message-list.js index 9fcf183..1cb411d 100644 --- a/src/vaadin-message-list.js +++ b/src/vaadin-message-list.js @@ -86,7 +86,7 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) { _itemsChanged(newVal, oldVal) { if ( (!oldVal || newVal.length > oldVal.length) && // there are new items - this.offsetTop + this.offsetHeight < window.pageYOffset + window.innerHeight // bottom of window + this.scrollHeight < this.clientHeight + this.scrollTop + 50 // bottom of list ) { microTask.run(() => this._scrollToLastMessage()); } @@ -94,7 +94,7 @@ class MessageListElement extends ElementMixin(ThemableMixin(PolymerElement)) { _scrollToLastMessage() { if (this.items.length > 0) { - this.shadowRoot.querySelectorAll('vaadin-message')[this.items.length - 1].scrollIntoView(); + this.scrollTop = this.scrollHeight - this.clientHeight; } } From 54f0266db02923b484b719a0d3878dd0847f02e6 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Mon, 8 Feb 2021 13:35:50 +0100 Subject: [PATCH 4/4] Add tests --- test/message-list.test.js | 60 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/test/message-list.test.js b/test/message-list.test.js index 8f525f8..d10579d 100644 --- a/test/message-list.test.js +++ b/test/message-list.test.js @@ -31,6 +31,26 @@ describe('message-list', () => { colorIndex: 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', @@ -59,7 +79,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', () => { @@ -75,5 +95,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); + }); }); });