@cloudflare/realtimekit-ui 1.1.0-staging.5 → 1.1.0-staging.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/browser.js +1 -1
  2. package/dist/cjs/rtk-avatar_24.cjs.entry.js +186 -175
  3. package/dist/cjs/rtk-chat-toggle.cjs.entry.js +2 -2
  4. package/dist/cjs/rtk-notifications.cjs.entry.js +4 -1
  5. package/dist/collection/components/rtk-chat-messages-ui-paginated/rtk-chat-messages-ui-paginated.js +2 -2
  6. package/dist/collection/components/rtk-chat-toggle/rtk-chat-toggle.js +2 -2
  7. package/dist/collection/components/rtk-notifications/rtk-notifications.js +4 -1
  8. package/dist/collection/components/rtk-paginated-list/rtk-paginated-list.js +184 -173
  9. package/dist/components/{p-32c6e86d.js → p-1d16490e.js} +2 -2
  10. package/dist/components/{p-ae376177.js → p-7be71567.js} +3 -3
  11. package/dist/components/{p-0d472019.js → p-7f8d9afc.js} +184 -173
  12. package/dist/components/rtk-chat-messages-ui-paginated.js +1 -1
  13. package/dist/components/rtk-chat-search-results.js +1 -1
  14. package/dist/components/rtk-chat-toggle.js +2 -2
  15. package/dist/components/rtk-chat.js +1 -1
  16. package/dist/components/rtk-meeting.js +3 -3
  17. package/dist/components/rtk-notifications.js +4 -1
  18. package/dist/components/rtk-paginated-list.js +1 -1
  19. package/dist/docs/docs-components.json +1 -1
  20. package/dist/esm/loader.js +191 -177
  21. package/dist/esm/rtk-avatar_24.entry.js +186 -175
  22. package/dist/esm/rtk-chat-toggle.entry.js +2 -2
  23. package/dist/esm/rtk-notifications.entry.js +4 -1
  24. package/dist/realtimekit-ui/p-25c13ff8.entry.js +1 -0
  25. package/dist/realtimekit-ui/p-342b4926.entry.js +1 -0
  26. package/dist/realtimekit-ui/{p-f457ae6f.entry.js → p-ec5ed8a4.entry.js} +1 -1
  27. package/dist/realtimekit-ui/realtimekit-ui.esm.js +1 -1
  28. package/dist/types/components/rtk-paginated-list/rtk-paginated-list.d.ts +32 -46
  29. package/package.json +1 -1
  30. package/dist/realtimekit-ui/p-83db4de1.entry.js +0 -1
  31. package/dist/realtimekit-ui/p-a859d883.entry.js +0 -1
@@ -1,4 +1,4 @@
1
- import { p as proxyCustomElement, H, w as writeTask, h, e as Host } from './p-c3592601.js';
1
+ import { p as proxyCustomElement, H, h, e as Host } from './p-c3592601.js';
2
2
  import { e as defaultIconPack, i as useLanguage } from './p-e847fee9.js';
3
3
  import { S as SyncWithStore } from './p-6f7c46d2.js';
4
4
  import { d as defineCustomElement$3 } from './p-1391bef0.js';
@@ -10,32 +10,30 @@ const rtkPaginatedListCss = ".scrollbar{scrollbar-width:thin;scrollbar-color:var
10
10
  const RtkPaginatedListStyle0 = rtkPaginatedListCss;
11
11
 
12
12
  /**
13
- * HOW INFINITE SCROLL WORKS:
13
+ * NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
14
14
  *
15
- * We use intersectionObserver to scroll up.
16
- * We use scrollEnd listener to scroll down.
15
+ * Uses scrollend listener for 2way scrolling.
16
+ * Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
17
17
  *
18
- * Why?
19
- * intersectionObserver doesn't work reliably for 2 way scrolling but has great ux,
20
- * so we use it to smoothly scroll up.
18
+ * UPWARD SCROLLING:
19
+ * - Fetch top anchor (element currently visible to the user near top)
20
+ * - Fetch older messages, push to end of 2D array
21
+ * - When exceeding pagesAllowed, delete pages and scroll back to anchor
21
22
  *
22
- * We have empty divs at the top and bottom ($topRef, $bottomRef)
23
- * which act as triggers to tell that we have reached the top or end of our messages and need to fetch new messages,
23
+ * DOWNWARD SCROLLING:
24
+ * - Fetch bottom anchor (element currently visible to the user near bottom)
25
+ * - Fetch new page, insert at the start
26
+ * - Update timestamps & firstEmptyIndex, then rerender
27
+ * - When exceeding pagesAllowed, delete pages and scroll back to anchor
24
28
  *
25
- * When scrolling up, we can't remove pages as intersectionObserver relies on
26
- * the index of dom elements to work properly.
27
- * So instead, we fetch older messages and push them to the end of the 2d array
28
- * if length exceeds pagesAllowed, we free up the pages and keep the first empty index in memory (firstEmptyIndex).
29
+ * ADDING NEW NODES:
30
+ * - If no pages exist, load old page
31
+ * - If on 1st page, append messages till page size is full and then load new page
29
32
  *
30
- * For scrolling down, when scroll ends we see if the bottomRef is in view.
31
- * If yes, we fetch the new page and insert it at the firstEmptyIndex.
32
- * We update timestamps & firstEmptyIndex, then we rerender.
33
- *
34
- * If we have exceeded our page allowance we delete old pages.
35
- *
36
- * In this case deleting pages is okay as we are not relying on the index of dom elements to detect page end.
37
- *
38
- * This also simplifies the code because when a user scrolls up we do not need to manage a lastEmptyIndex.
33
+ * DELETE NODE:
34
+ * - If deleting the only available node, reset to initial state
35
+ * - If page is empty, delete it
36
+ * - Update timestamp curors
39
37
  */
40
38
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
41
39
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -52,43 +50,27 @@ const RtkPaginatedList = /*@__PURE__*/ proxyCustomElement(class RtkPaginatedList
52
50
  super();
53
51
  this.__registerHost();
54
52
  this.__attachShadow();
55
- /**
56
- * when scrolling up, we can't remove pages as intersectionObserver relies on
57
- * the index of dom elements to stay stable.
58
- * So, instead we free up the pages and keep the last empty index in memory.
59
- */
60
- this.firstEmptyIndex = -1;
61
- this.maxTS = 0;
62
53
  // the length of pages will always be pageSize + 2
63
54
  this.pages = [];
55
+ // Controls whether to keep auto-scrolling when a new page load.
56
+ this.shouldScrollToBottom = false;
57
+ // Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
58
+ this.showNewMessagesCTR = false;
64
59
  /** label to show when empty */
65
60
  this.emptyListLabel = null;
66
- this.rerenderBoolean = false;
67
- this.showEmptyListLabel = false;
68
61
  /** Icon pack */
69
62
  this.iconPack = defaultIconPack;
70
63
  /** Language */
71
64
  this.t = useLanguage();
65
+ this.rerenderBoolean = false;
66
+ this.showEmptyListLabel = false;
72
67
  this.isLoading = false;
73
68
  this.isLoadingTop = false;
74
69
  this.isLoadingBottom = false;
75
- /**
76
- * Even when auto scroll is enabled, we only want to scroll if a new realtime message has arrived.
77
- * This variable tells us if we should respect auto scroll after a new page has been loaded.
78
- * It is also used by the scroll to bottom button.
79
- * */
80
- this.shouldScrollToBottom = false;
81
- /** UI Indicator for the "scroll to bottom" button.
82
- * Toggles on when a new node is added and autoscroll is disabled.
83
- * Toggles off when all nodes are loaded */
84
- this.showNewMessagesCTR = false;
85
- this.observe = (el) => {
86
- if (!el)
87
- return;
88
- this.intersectionObserver.observe(el);
89
- };
90
- this.isAtBottom = () => {
91
- const rect = this.$bottomRef.getBoundingClientRect();
70
+ // Tells us if we need to scroll to a specific anchor after a rerender
71
+ this.pendingScrollAnchor = null;
72
+ this.isInView = (el) => {
73
+ const rect = el.getBoundingClientRect();
92
74
  return rect.top >= 0 && rect.bottom <= window.innerHeight;
93
75
  };
94
76
  }
@@ -97,10 +79,12 @@ const RtkPaginatedList = /*@__PURE__*/ proxyCustomElement(class RtkPaginatedList
97
79
  * @param {DataNode} node - The data node to add to the beginning of the list
98
80
  */
99
81
  async onNewNode(node) {
100
- // Always update the maxTS. New messages will load on scroll till the end cursor (newTS) reaches this value.
101
- this.maxTS = Math.max(this.maxTS, node.timeMs);
102
- // if we are at the bottom of the page
103
- if (this.firstEmptyIndex === -1) {
82
+ // if there are no pages, load the first page
83
+ if (this.pages.length < 1) {
84
+ this.oldTS = node.timeMs + 1;
85
+ this.loadPrevPage();
86
+ }
87
+ else {
104
88
  // append messages to the page if page has not reached full capacity
105
89
  if (this.pages[0].length < this.pageSize) {
106
90
  this.pages[0].unshift(node);
@@ -112,49 +96,40 @@ const RtkPaginatedList = /*@__PURE__*/ proxyCustomElement(class RtkPaginatedList
112
96
  this.loadNextPage();
113
97
  }
114
98
  }
115
- // If autoscroll is enabled, this method will scroll to the bottom
99
+ // If autoscroll is enabled, scroll to the bottom
116
100
  if (this.autoScroll) {
117
101
  this.shouldScrollToBottom = true;
118
102
  this.scrollToBottom();
119
103
  }
120
- else {
121
- this.showNewMessagesCTR = true;
122
- }
123
- }
124
- // this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
125
- scrollToBottom() {
126
- this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
127
104
  }
128
105
  /**
129
106
  * Deletes a node anywhere from the list
130
107
  * @param {string} id - The id of the node to delete
131
108
  * */
132
109
  async onNodeDelete(id) {
133
- // Iterate only over pages that have content (not empty)
134
- for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
110
+ var _a, _b;
111
+ let didDelete = false;
112
+ for (let i = this.pages.length - 1; i >= 0; i--) {
135
113
  const index = this.pages[i].findIndex((node) => node.id === id);
136
- // message in view
137
- if (index !== -1) {
138
- // delete message
139
- this.pages[i].splice(index, 1);
140
- if (i === this.firstEmptyIndex + 1) {
141
- // if newest page is empty, update first empty index
142
- if (this.pages[i].length === 0)
143
- this.firstEmptyIndex++;
144
- // update timestamp, first empty index could be -1, so we need to cap it at 0
145
- this.newTS = this.pages[Math.max(this.firstEmptyIndex, 0)][0].timeMs;
146
- }
147
- else if (i === this.firstEmptyIndex + this.pagesAllowed) {
148
- // if oldest page is empty, remove it
149
- if (this.pages[i].length === 0)
150
- this.pages.pop();
151
- // update timestamp
152
- const lastPage = this.pages[this.firstEmptyIndex + this.pagesAllowed];
153
- this.oldTS = lastPage[lastPage.length - 1].timeMs;
154
- }
155
- this.rerender();
156
- }
114
+ // if message not found, move on
115
+ if (index === -1)
116
+ continue;
117
+ // delete message
118
+ this.pages[i].splice(index, 1);
119
+ // if page is empty, delete it
120
+ if (this.pages[i].length === 0)
121
+ this.pages.splice(i, 1);
122
+ didDelete = true;
123
+ break;
157
124
  }
125
+ if (!didDelete)
126
+ return;
127
+ // update timestamps
128
+ const firstPage = this.pages[0];
129
+ const lastPage = this.pages[this.pages.length - 1];
130
+ this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
131
+ this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
132
+ this.rerender();
158
133
  }
159
134
  /**
160
135
  * Updates a new node anywhere in the list
@@ -162,146 +137,182 @@ const RtkPaginatedList = /*@__PURE__*/ proxyCustomElement(class RtkPaginatedList
162
137
  * @param {DataNode} _node - The updated data node
163
138
  * */
164
139
  async onNodeUpdate(_id, _node) { }
165
- rerender() {
166
- this.rerenderBoolean = !this.rerenderBoolean;
167
- }
168
140
  connectedCallback() {
169
141
  this.rerender = debounce(this.rerender.bind(this), 50, { maxWait: 200 });
170
- this.intersectionObserver = new IntersectionObserver((entries) => {
171
- writeTask(async () => {
172
- for (const entry of entries) {
173
- if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
174
- this.isLoadingTop = true;
175
- await this.loadPrevPage();
176
- this.isLoadingTop = false;
177
- }
178
- }
179
- });
180
- });
181
142
  }
182
143
  componentDidLoad() {
183
- this.observe(this.$topRef);
144
+ // initial load
145
+ this.loadPrevPage();
184
146
  if (this.$containerRef) {
185
147
  this.$containerRef.onscrollend = async () => {
186
- /**
187
- * Load new page if:
188
- * if there are nodes to load at the bottom (maxTS > newTS)
189
- * or if there are pages to fill at the bottom (firstEmptyIndex > -1)
190
- */
191
- if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
192
- this.isLoadingBottom = true;
148
+ if (this.isInView(this.$bottomRef)) {
193
149
  await this.loadNextPage();
194
- this.isLoadingBottom = false;
195
- if (this.shouldScrollToBottom)
196
- this.scrollToBottom();
150
+ }
151
+ else if (this.isInView(this.$topRef)) {
152
+ this.showNewMessagesCTR = true;
153
+ await this.loadPrevPage();
197
154
  }
198
155
  };
199
156
  }
200
157
  }
158
+ componentDidRender() {
159
+ if (!this.pendingScrollAnchor)
160
+ return;
161
+ const anchor = this.pendingScrollAnchor;
162
+ this.pendingScrollAnchor = null;
163
+ this.restoreScrollToAnchor(anchor);
164
+ }
201
165
  async loadPrevPage() {
202
166
  if (this.isLoading)
203
167
  return;
204
- /**
205
- * NOTE(ikabra): this case also runs on initial load
206
- * if scrolling up ->
207
- * fetch older messages and push to the end of the array
208
- * cleanup 1st non empty page from the array if length exceeds pagesAllowed
209
- */
168
+ const scrollAnchor = this.getScrollAnchor('top');
210
169
  // if no old timestamp, it means we are at initial state
211
170
  if (!this.oldTS)
212
171
  this.oldTS = new Date().getTime();
213
172
  // load data
214
173
  this.isLoading = true;
174
+ this.isLoadingTop = true;
215
175
  const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
216
176
  this.isLoading = false;
177
+ this.isLoadingTop = false;
217
178
  // no more old messages to show, we are at the top of the page
218
179
  if (!data.length)
219
180
  return;
220
181
  // add old data to the end of the array
221
182
  this.pages.push(data);
222
183
  // clear old pages when we reach the limit
223
- if (this.pages.length > this.pagesAllowed) {
224
- this.pages[this.pages.length - this.pagesAllowed - 1] = [];
225
- /**
226
- * find last non empty page in range (this.pages.length, this.firstEmptyIndex)
227
- * we are doing this because any of the middle pages in the currently rendered pages
228
- * could be empty as we allow deleting messages.
229
- * This helps us set the first empty index correctly.
230
- */
231
- for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
232
- if (this.pages[i].length > 0)
233
- break;
234
- this.firstEmptyIndex = i;
235
- }
236
- }
237
- // update the old timestamp
184
+ if (this.pages.length > this.pagesAllowed)
185
+ this.pages.shift();
186
+ // update timestamps
238
187
  const lastPage = this.pages[this.pages.length - 1];
239
188
  this.oldTS = lastPage[lastPage.length - 1].timeMs;
240
- // update the new timestamp
241
- this.newTS = this.pages[this.firstEmptyIndex + 1][0].timeMs;
189
+ this.newTS = this.pages[0][0].timeMs;
242
190
  this.rerender();
191
+ // fix scroll position
192
+ if (scrollAnchor)
193
+ this.pendingScrollAnchor = scrollAnchor;
243
194
  }
244
195
  async loadNextPage() {
245
196
  if (this.isLoading)
246
197
  return;
247
- // new timestamp needs to be assigned by loadPrevPage method
198
+ // Do nothing. New timestamp needs to be assigned by loadPrevPage method
248
199
  if (!this.newTS) {
249
200
  this.showNewMessagesCTR = false;
250
201
  this.shouldScrollToBottom = false;
251
202
  return;
252
203
  }
253
- // load data
204
+ // for autoscroll or scroll to bottom button
205
+ const maxAutoLoads = 200;
206
+ let loads = 0;
207
+ let prevNewTS = this.newTS;
254
208
  this.isLoading = true;
255
- const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
256
- this.isLoading = false;
257
- // no more new messages to load
258
- if (!data.length) {
259
- this.showNewMessagesCTR = false;
260
- this.shouldScrollToBottom = false;
261
- // remove extra pages from the start if any (could be due to users deleting messages)
262
- this.pages = this.pages.filter((page) => page.length > 0);
263
- this.firstEmptyIndex = -1;
264
- return;
265
- }
266
- // when filling empty pages
267
- if (this.firstEmptyIndex > -1) {
268
- this.pages[this.firstEmptyIndex] = data.reverse();
269
- }
270
- else {
271
- // when already at the bottom and loading messages in realtime
209
+ this.isLoadingBottom = true;
210
+ while (loads < maxAutoLoads) {
211
+ const scrollAnchor = this.getScrollAnchor('bottom');
212
+ const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
213
+ this.isLoading = false;
214
+ this.isLoadingBottom = false;
215
+ // no more new messages to load
216
+ if (!data.length) {
217
+ this.showNewMessagesCTR = false;
218
+ this.shouldScrollToBottom = false;
219
+ break;
220
+ }
221
+ // load new messages and append to the start
272
222
  this.pages.unshift(data.reverse());
223
+ // remove pages if out of bounds
224
+ if (this.pages.length > this.pagesAllowed)
225
+ this.pages.pop();
226
+ // update timestamps
227
+ const lastPage = this.pages[this.pages.length - 1];
228
+ this.oldTS = lastPage[lastPage.length - 1].timeMs;
229
+ this.newTS = this.pages[0][0].timeMs;
230
+ this.rerender();
231
+ this.pendingScrollAnchor = scrollAnchor;
232
+ if (!this.shouldScrollToBottom)
233
+ break;
234
+ // if should scroll to bottom then retrigger
235
+ await this.waitForNextFrame();
236
+ this.scrollToBottom();
237
+ await this.waitForNextFrame();
238
+ // if no new messages, break
239
+ if (this.newTS === prevNewTS)
240
+ break;
241
+ prevNewTS = this.newTS;
242
+ loads++;
273
243
  }
274
- if (this.pages.length > this.pagesAllowed) {
275
- this.pages.pop();
244
+ }
245
+ // Find the element that is closest to the top/bottom of the container
246
+ getScrollAnchor(edge = 'top') {
247
+ if (!this.$containerRef)
248
+ return null;
249
+ const containerRect = this.$containerRef.getBoundingClientRect();
250
+ const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
251
+ let best = null;
252
+ for (const el of candidates) {
253
+ const rect = el.getBoundingClientRect();
254
+ const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
255
+ if (!isVisibleInContainer)
256
+ continue;
257
+ if (edge === 'top') {
258
+ const offsetTop = rect.top - containerRect.top;
259
+ if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
260
+ best = { id: el.id, edge: 'top', offsetTop };
261
+ }
262
+ }
263
+ else {
264
+ const offsetBottom = containerRect.bottom - rect.bottom;
265
+ if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
266
+ best = { id: el.id, edge: 'bottom', offsetBottom };
267
+ }
268
+ }
276
269
  }
277
- // smallest value for firstEmptyIndex can be -1, so we cap the index at 0
278
- this.newTS = this.pages[Math.max(0, this.firstEmptyIndex)][0].timeMs;
279
- // remove all empty pages from the end
280
- for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
281
- if (this.pages[i].length > 0)
282
- break;
283
- // if page is empty, remove it
284
- this.pages.pop();
270
+ return best;
271
+ }
272
+ //instant scroll to anchor to make sure we are at the same position after a rerender
273
+ restoreScrollToAnchor(anchor) {
274
+ if (!this.$containerRef)
275
+ return;
276
+ // make element id safe to use inside a CSS selector
277
+ const escapeId = (id) => {
278
+ var _a;
279
+ const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
280
+ return typeof cssEscape === 'function'
281
+ ? cssEscape(id)
282
+ : id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
283
+ };
284
+ const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
285
+ if (!el)
286
+ return;
287
+ const containerRect = this.$containerRef.getBoundingClientRect();
288
+ const rect = el.getBoundingClientRect();
289
+ if (anchor.edge === 'top') {
290
+ const newOffsetTop = rect.top - containerRect.top;
291
+ this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
285
292
  }
286
- // update the old timestamp
287
- const lastPage = this.pages[this.pages.length - 1];
288
- this.oldTS = lastPage[lastPage.length - 1].timeMs;
289
- // when scrolling too fast scroll a bit to the top to be able to load new messages when you scroll down
290
- if (this.$containerRef.scrollTop === 0)
291
- this.$containerRef.scrollTop = -60;
292
- // smallest value for this index can be -1 (indicates we are at the bottom of the page).
293
- this.firstEmptyIndex = Math.max(-1, this.firstEmptyIndex - 1);
294
- this.rerender();
293
+ else {
294
+ const newOffsetBottom = containerRect.bottom - rect.bottom;
295
+ this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
296
+ }
297
+ }
298
+ // this method is called recursively based on shouldScrollToBottom (see loadNextPage)
299
+ scrollToBottom() {
300
+ this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
301
+ }
302
+ waitForNextFrame() {
303
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
304
+ }
305
+ rerender() {
306
+ this.rerenderBoolean = !this.rerenderBoolean;
295
307
  }
296
308
  render() {
297
309
  /**
298
- * div.container is flex=column-reverse
299
- * which is why div#bottom-scroll comes before div#top-scroll
310
+ * div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
300
311
  */
301
- return (h(Host, { key: '91ac7d0ca3fb720259945ffaa97f465b34c694fa' }, h("div", { key: '33896c19ecc4359ae163c65b5c71b9f17673e765', class: "scrollbar container", part: "container", ref: (el) => (this.$containerRef = el) }, h("div", { key: 'e26a5ef3979ec132277b9598afc17ea65683f6c8', class: { 'show-new-messages-ctr': true, active: this.showNewMessagesCTR } }, h("rtk-button", { key: 'e769a8f54a298af456552733dc9de27d059e5138', class: "show-new-messages", kind: "icon", variant: "secondary", part: "show-new-messages", onClick: () => {
312
+ return (h(Host, { key: 'e0f806cccdcba162d0c834476863b34630cb1a1e' }, h("div", { key: '6d54d50ed703a59df8d26399499533e3cb0d70fe', class: "scrollbar container", part: "container", ref: (el) => (this.$containerRef = el) }, h("div", { key: '4e9fcbed725fb55d9fbb67eca94b0e770662d51b', class: { 'show-new-messages-ctr': true, active: this.showNewMessagesCTR } }, h("rtk-button", { key: '7db0236d35db3fb9856fea7a4f62c1fbd421829e', class: "show-new-messages", kind: "icon", variant: "secondary", part: "show-new-messages", onClick: () => {
302
313
  this.shouldScrollToBottom = true;
303
314
  this.scrollToBottom();
304
- } }, h("rtk-icon", { key: '6fb4cbc2247eb971004a94926b95ebd0f90ab0fd', icon: this.iconPack.chevron_down }))), h("div", { key: 'e91dd8f25012e4509e0ff3cb4d6b65aa9467d427', class: "smallest-dom-element", id: "bottom-scroll", ref: (el) => (this.$bottomRef = el) }), this.isLoadingBottom && this.pages.length > 0 && h("rtk-spinner", { key: '199c0ccffd57716bd5a05dcef8610113d3c58d3d', size: "sm" }), this.isLoading && this.pages.length < 1 && h("rtk-spinner", { key: 'b8a3e08a25b2bc1d50b5a9b1b2deda802ae5eb28', size: "lg" }), !this.isLoading && this.pages.flat().length === 0 ? (h("div", { class: "empty-list" }, this.t('list.empty'))) : (h("div", { class: "page-wrapper" }, this.pages.map((page, pageIndex) => (h("div", { class: "page", "data-page-index": pageIndex }, this.createNodes([...page].reverse())))))), this.isLoadingTop && this.pages.length > 0 && h("rtk-spinner", { key: '2cb56b4f70d37548fd9aa71b961559b43c54a922', size: "sm" }), h("div", { key: '4b183c49bfe60fd63af40e02b9b46215c08bb484', class: "smallest-dom-element", id: "top-scroll", ref: (el) => (this.$topRef = el) }))));
315
+ } }, h("rtk-icon", { key: '6e247e86029560601080e0b4d6dcfccbd90fcdd6', icon: this.iconPack.chevron_down }))), h("div", { key: '01d1ba7eacc67ece70b805fb2dfb493cdf1c9d23', class: "smallest-dom-element", id: "bottom-scroll", ref: (el) => (this.$bottomRef = el) }), this.isLoadingBottom && this.pages.length > 0 && h("rtk-spinner", { key: '24391bfc34914675cbfd0c287332bfdf3f5f5000', size: "sm" }), this.isLoading && this.pages.length < 1 && h("rtk-spinner", { key: 'e6c2cb44fce52ca54c9f1e543d91a29648745408', size: "lg" }), !this.isLoading && this.pages.flat().length === 0 ? (h("div", { class: "empty-list" }, this.t('list.empty'))) : (h("div", { class: "page-wrapper" }, this.pages.map((page, pageIndex) => (h("div", { class: "page", "data-page-index": pageIndex }, this.createNodes([...page].reverse())))))), this.isLoadingTop && this.pages.length > 0 && h("rtk-spinner", { key: 'd90a15494bcd7ec6c9c7ffc5e9a55054252a4258', size: "sm" }), h("div", { key: '2a65f98c3c4e6f2510c6cf1b4e2bcf1e607a7552', class: "smallest-dom-element", id: "top-scroll", ref: (el) => (this.$topRef = el) }))));
305
316
  }
306
317
  static get style() { return RtkPaginatedListStyle0; }
307
318
  }, [1, "rtk-paginated-list", {
@@ -1,4 +1,4 @@
1
- import { R as RtkChatMessagesUiPaginated$1, d as defineCustomElement$1 } from './p-ae376177.js';
1
+ import { R as RtkChatMessagesUiPaginated$1, d as defineCustomElement$1 } from './p-7be71567.js';
2
2
 
3
3
  const RtkChatMessagesUiPaginated = RtkChatMessagesUiPaginated$1;
4
4
  const defineCustomElement = defineCustomElement$1;
@@ -11,7 +11,7 @@ import { d as defineCustomElement$8 } from './p-fa476519.js';
11
11
  import { d as defineCustomElement$7 } from './p-2447a26f.js';
12
12
  import { d as defineCustomElement$6 } from './p-819cb785.js';
13
13
  import { d as defineCustomElement$5 } from './p-7148ec6a.js';
14
- import { d as defineCustomElement$4 } from './p-0d472019.js';
14
+ import { d as defineCustomElement$4 } from './p-7f8d9afc.js';
15
15
  import { d as defineCustomElement$3 } from './p-4ebf9684.js';
16
16
  import { d as defineCustomElement$2 } from './p-46d99dd9.js';
17
17
 
@@ -95,9 +95,9 @@ const RtkChatToggle$1 = /*@__PURE__*/ proxyCustomElement(class RtkChatToggle ext
95
95
  const { messages } = await chat.getMessages(new Date().getTime(), this.pageSize, true);
96
96
  const meetingStartedTimeMs = (_b = (_a = this.meeting.meta) === null || _a === void 0 ? void 0 : _a.meetingStartedTimestamp.getTime()) !== null && _b !== void 0 ? _b : 0;
97
97
  const newMessages = messages.filter((m) => m.timeMs > meetingStartedTimeMs);
98
- if (newMessages.length === messages.length && messages.length > 0) {
98
+ if (newMessages.length === this.pageSize && newMessages.length > 0) {
99
99
  // all messages are new, so we can't know the exact count, but we know there are at least pageSize - 1 new messages
100
- this.unreadMessageCount = this.pageSize - 1;
100
+ this.unreadMessageCount = newMessages.length;
101
101
  }
102
102
  else {
103
103
  this.unreadMessageCount = newMessages.length;
@@ -1,4 +1,4 @@
1
- import { R as RtkChat$1, d as defineCustomElement$1 } from './p-32c6e86d.js';
1
+ import { R as RtkChat$1, d as defineCustomElement$1 } from './p-1d16490e.js';
2
2
 
3
3
  const RtkChat = RtkChat$1;
4
4
  const defineCustomElement = defineCustomElement$1;
@@ -8,9 +8,9 @@ import { i as index } from './p-f47d4fe8.js';
8
8
  import { d as defineCustomElement$o } from './p-241a8245.js';
9
9
  import { d as defineCustomElement$n } from './p-1391bef0.js';
10
10
  import { d as defineCustomElement$m } from './p-a73665b4.js';
11
- import { d as defineCustomElement$l } from './p-32c6e86d.js';
11
+ import { d as defineCustomElement$l } from './p-1d16490e.js';
12
12
  import { d as defineCustomElement$k } from './p-28170a8d.js';
13
- import { d as defineCustomElement$j } from './p-ae376177.js';
13
+ import { d as defineCustomElement$j } from './p-7be71567.js';
14
14
  import { d as defineCustomElement$i } from './p-1f5a4682.js';
15
15
  import { d as defineCustomElement$h } from './p-598dc3f2.js';
16
16
  import { d as defineCustomElement$g } from './p-0e5cc539.js';
@@ -23,7 +23,7 @@ import { d as defineCustomElement$a } from './p-2447a26f.js';
23
23
  import { d as defineCustomElement$9 } from './p-819cb785.js';
24
24
  import { d as defineCustomElement$8 } from './p-7148ec6a.js';
25
25
  import { d as defineCustomElement$7 } from './p-0f2de0f8.js';
26
- import { d as defineCustomElement$6 } from './p-0d472019.js';
26
+ import { d as defineCustomElement$6 } from './p-7f8d9afc.js';
27
27
  import { d as defineCustomElement$5 } from './p-4ebf9684.js';
28
28
  import { d as defineCustomElement$4 } from './p-4902c5cf.js';
29
29
  import { d as defineCustomElement$3 } from './p-6739a399.js';
@@ -233,7 +233,10 @@ const RtkNotifications$1 = /*@__PURE__*/ proxyCustomElement(class RtkNotificatio
233
233
  this.waitlistedParticipantLeftListener = (participant) => {
234
234
  this.remove(`${participant.id}-joined-waitlist`);
235
235
  };
236
- this.chatUpdateListener = ({ message }) => {
236
+ this.chatUpdateListener = ({ message, action }) => {
237
+ // NOTE(ikabra): we only want notifications for new messages
238
+ if (action !== 'add')
239
+ return;
237
240
  const parsedMessage = parseMessageForTarget(message);
238
241
  if (parsedMessage != null) {
239
242
  if (parsedMessage.userId === meeting.self.userId) {
@@ -1,4 +1,4 @@
1
- import { R as RtkPaginatedList$1, d as defineCustomElement$1 } from './p-0d472019.js';
1
+ import { R as RtkPaginatedList$1, d as defineCustomElement$1 } from './p-7f8d9afc.js';
2
2
 
3
3
  const RtkPaginatedList = RtkPaginatedList$1;
4
4
  const defineCustomElement = defineCustomElement$1;
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-16T10:29:31",
2
+ "timestamp": "2026-01-20T04:51:21",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.27.2",