@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,30 +1,28 @@
1
1
  /**
2
- * HOW INFINITE SCROLL WORKS:
2
+ * NOTE(ikabra): INFINITE SCROLL IMPLEMENTATION:
3
3
  *
4
- * We use intersectionObserver to scroll up.
5
- * We use scrollEnd listener to scroll down.
4
+ * Uses scrollend listener for 2way scrolling.
5
+ * Empty divs ($topRef, $bottomRef) act as scroll triggers to fetch new messages.
6
6
  *
7
- * Why?
8
- * intersectionObserver doesn't work reliably for 2 way scrolling but has great ux,
9
- * so we use it to smoothly scroll up.
7
+ * UPWARD SCROLLING:
8
+ * - Fetch top anchor (element currently visible to the user near top)
9
+ * - Fetch older messages, push to end of 2D array
10
+ * - When exceeding pagesAllowed, delete pages and scroll back to anchor
10
11
  *
11
- * We have empty divs at the top and bottom ($topRef, $bottomRef)
12
- * which act as triggers to tell that we have reached the top or end of our messages and need to fetch new messages,
12
+ * DOWNWARD SCROLLING:
13
+ * - Fetch bottom anchor (element currently visible to the user near bottom)
14
+ * - Fetch new page, insert at the start
15
+ * - Update timestamps & firstEmptyIndex, then rerender
16
+ * - When exceeding pagesAllowed, delete pages and scroll back to anchor
13
17
  *
14
- * When scrolling up, we can't remove pages as intersectionObserver relies on
15
- * the index of dom elements to work properly.
16
- * So instead, we fetch older messages and push them to the end of the 2d array
17
- * if length exceeds pagesAllowed, we free up the pages and keep the first empty index in memory (firstEmptyIndex).
18
+ * ADDING NEW NODES:
19
+ * - If no pages exist, load old page
20
+ * - If on 1st page, append messages till page size is full and then load new page
18
21
  *
19
- * For scrolling down, when scroll ends we see if the bottomRef is in view.
20
- * If yes, we fetch the new page and insert it at the firstEmptyIndex.
21
- * We update timestamps & firstEmptyIndex, then we rerender.
22
- *
23
- * If we have exceeded our page allowance we delete old pages.
24
- *
25
- * In this case deleting pages is okay as we are not relying on the index of dom elements to detect page end.
26
- *
27
- * This also simplifies the code because when a user scrolls up we do not need to manage a lastEmptyIndex.
22
+ * DELETE NODE:
23
+ * - If deleting the only available node, reset to initial state
24
+ * - If page is empty, delete it
25
+ * - Update timestamp curors
28
26
  */
29
27
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
30
28
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -36,50 +34,34 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
36
34
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
37
35
  return c > 3 && r && Object.defineProperty(target, key, r), r;
38
36
  };
39
- import { Host, h, writeTask } from "@stencil/core";
37
+ import { Host, h } from "@stencil/core";
40
38
  import { defaultIconPack } from "../../lib/icons";
41
39
  import { useLanguage } from "../../lib/lang";
42
40
  import { SyncWithStore } from "../../utils/sync-with-store";
43
41
  import { debounce } from "lodash-es";
44
42
  export class RtkPaginatedList {
45
43
  constructor() {
46
- /**
47
- * when scrolling up, we can't remove pages as intersectionObserver relies on
48
- * the index of dom elements to stay stable.
49
- * So, instead we free up the pages and keep the last empty index in memory.
50
- */
51
- this.firstEmptyIndex = -1;
52
- this.maxTS = 0;
53
44
  // the length of pages will always be pageSize + 2
54
45
  this.pages = [];
46
+ // Controls whether to keep auto-scrolling when a new page load.
47
+ this.shouldScrollToBottom = false;
48
+ // Shows "scroll to bottom" button when new nodes arrive and autoscroll is off.
49
+ this.showNewMessagesCTR = false;
55
50
  /** label to show when empty */
56
51
  this.emptyListLabel = null;
57
- this.rerenderBoolean = false;
58
- this.showEmptyListLabel = false;
59
52
  /** Icon pack */
60
53
  this.iconPack = defaultIconPack;
61
54
  /** Language */
62
55
  this.t = useLanguage();
56
+ this.rerenderBoolean = false;
57
+ this.showEmptyListLabel = false;
63
58
  this.isLoading = false;
64
59
  this.isLoadingTop = false;
65
60
  this.isLoadingBottom = false;
66
- /**
67
- * Even when auto scroll is enabled, we only want to scroll if a new realtime message has arrived.
68
- * This variable tells us if we should respect auto scroll after a new page has been loaded.
69
- * It is also used by the scroll to bottom button.
70
- * */
71
- this.shouldScrollToBottom = false;
72
- /** UI Indicator for the "scroll to bottom" button.
73
- * Toggles on when a new node is added and autoscroll is disabled.
74
- * Toggles off when all nodes are loaded */
75
- this.showNewMessagesCTR = false;
76
- this.observe = (el) => {
77
- if (!el)
78
- return;
79
- this.intersectionObserver.observe(el);
80
- };
81
- this.isAtBottom = () => {
82
- const rect = this.$bottomRef.getBoundingClientRect();
61
+ // Tells us if we need to scroll to a specific anchor after a rerender
62
+ this.pendingScrollAnchor = null;
63
+ this.isInView = (el) => {
64
+ const rect = el.getBoundingClientRect();
83
65
  return rect.top >= 0 && rect.bottom <= window.innerHeight;
84
66
  };
85
67
  }
@@ -88,10 +70,12 @@ export class RtkPaginatedList {
88
70
  * @param {DataNode} node - The data node to add to the beginning of the list
89
71
  */
90
72
  async onNewNode(node) {
91
- // Always update the maxTS. New messages will load on scroll till the end cursor (newTS) reaches this value.
92
- this.maxTS = Math.max(this.maxTS, node.timeMs);
93
- // if we are at the bottom of the page
94
- if (this.firstEmptyIndex === -1) {
73
+ // if there are no pages, load the first page
74
+ if (this.pages.length < 1) {
75
+ this.oldTS = node.timeMs + 1;
76
+ this.loadPrevPage();
77
+ }
78
+ else {
95
79
  // append messages to the page if page has not reached full capacity
96
80
  if (this.pages[0].length < this.pageSize) {
97
81
  this.pages[0].unshift(node);
@@ -103,49 +87,40 @@ export class RtkPaginatedList {
103
87
  this.loadNextPage();
104
88
  }
105
89
  }
106
- // If autoscroll is enabled, this method will scroll to the bottom
90
+ // If autoscroll is enabled, scroll to the bottom
107
91
  if (this.autoScroll) {
108
92
  this.shouldScrollToBottom = true;
109
93
  this.scrollToBottom();
110
94
  }
111
- else {
112
- this.showNewMessagesCTR = true;
113
- }
114
- }
115
- // this method is called recursively based on shouldScrollToBottom (see scrollEnd listener)
116
- scrollToBottom() {
117
- this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
118
95
  }
119
96
  /**
120
97
  * Deletes a node anywhere from the list
121
98
  * @param {string} id - The id of the node to delete
122
99
  * */
123
100
  async onNodeDelete(id) {
124
- // Iterate only over pages that have content (not empty)
125
- for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
101
+ var _a, _b;
102
+ let didDelete = false;
103
+ for (let i = this.pages.length - 1; i >= 0; i--) {
126
104
  const index = this.pages[i].findIndex((node) => node.id === id);
127
- // message in view
128
- if (index !== -1) {
129
- // delete message
130
- this.pages[i].splice(index, 1);
131
- if (i === this.firstEmptyIndex + 1) {
132
- // if newest page is empty, update first empty index
133
- if (this.pages[i].length === 0)
134
- this.firstEmptyIndex++;
135
- // update timestamp, first empty index could be -1, so we need to cap it at 0
136
- this.newTS = this.pages[Math.max(this.firstEmptyIndex, 0)][0].timeMs;
137
- }
138
- else if (i === this.firstEmptyIndex + this.pagesAllowed) {
139
- // if oldest page is empty, remove it
140
- if (this.pages[i].length === 0)
141
- this.pages.pop();
142
- // update timestamp
143
- const lastPage = this.pages[this.firstEmptyIndex + this.pagesAllowed];
144
- this.oldTS = lastPage[lastPage.length - 1].timeMs;
145
- }
146
- this.rerender();
147
- }
105
+ // if message not found, move on
106
+ if (index === -1)
107
+ continue;
108
+ // delete message
109
+ this.pages[i].splice(index, 1);
110
+ // if page is empty, delete it
111
+ if (this.pages[i].length === 0)
112
+ this.pages.splice(i, 1);
113
+ didDelete = true;
114
+ break;
148
115
  }
116
+ if (!didDelete)
117
+ return;
118
+ // update timestamps
119
+ const firstPage = this.pages[0];
120
+ const lastPage = this.pages[this.pages.length - 1];
121
+ this.newTS = (_a = firstPage === null || firstPage === void 0 ? void 0 : firstPage[0]) === null || _a === void 0 ? void 0 : _a.timeMs;
122
+ this.oldTS = (_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage[lastPage.length - 1]) === null || _b === void 0 ? void 0 : _b.timeMs;
123
+ this.rerender();
149
124
  }
150
125
  /**
151
126
  * Updates a new node anywhere in the list
@@ -153,146 +128,182 @@ export class RtkPaginatedList {
153
128
  * @param {DataNode} _node - The updated data node
154
129
  * */
155
130
  async onNodeUpdate(_id, _node) { }
156
- rerender() {
157
- this.rerenderBoolean = !this.rerenderBoolean;
158
- }
159
131
  connectedCallback() {
160
132
  this.rerender = debounce(this.rerender.bind(this), 50, { maxWait: 200 });
161
- this.intersectionObserver = new IntersectionObserver((entries) => {
162
- writeTask(async () => {
163
- for (const entry of entries) {
164
- if (entry.target.id === 'top-scroll' && entry.isIntersecting) {
165
- this.isLoadingTop = true;
166
- await this.loadPrevPage();
167
- this.isLoadingTop = false;
168
- }
169
- }
170
- });
171
- });
172
133
  }
173
134
  componentDidLoad() {
174
- this.observe(this.$topRef);
135
+ // initial load
136
+ this.loadPrevPage();
175
137
  if (this.$containerRef) {
176
138
  this.$containerRef.onscrollend = async () => {
177
- /**
178
- * Load new page if:
179
- * if there are nodes to load at the bottom (maxTS > newTS)
180
- * or if there are pages to fill at the bottom (firstEmptyIndex > -1)
181
- */
182
- if (this.isAtBottom() && (this.maxTS > this.newTS || this.firstEmptyIndex > -1)) {
183
- this.isLoadingBottom = true;
139
+ if (this.isInView(this.$bottomRef)) {
184
140
  await this.loadNextPage();
185
- this.isLoadingBottom = false;
186
- if (this.shouldScrollToBottom)
187
- this.scrollToBottom();
141
+ }
142
+ else if (this.isInView(this.$topRef)) {
143
+ this.showNewMessagesCTR = true;
144
+ await this.loadPrevPage();
188
145
  }
189
146
  };
190
147
  }
191
148
  }
149
+ componentDidRender() {
150
+ if (!this.pendingScrollAnchor)
151
+ return;
152
+ const anchor = this.pendingScrollAnchor;
153
+ this.pendingScrollAnchor = null;
154
+ this.restoreScrollToAnchor(anchor);
155
+ }
192
156
  async loadPrevPage() {
193
157
  if (this.isLoading)
194
158
  return;
195
- /**
196
- * NOTE(ikabra): this case also runs on initial load
197
- * if scrolling up ->
198
- * fetch older messages and push to the end of the array
199
- * cleanup 1st non empty page from the array if length exceeds pagesAllowed
200
- */
159
+ const scrollAnchor = this.getScrollAnchor('top');
201
160
  // if no old timestamp, it means we are at initial state
202
161
  if (!this.oldTS)
203
162
  this.oldTS = new Date().getTime();
204
163
  // load data
205
164
  this.isLoading = true;
165
+ this.isLoadingTop = true;
206
166
  const data = await this.fetchData(this.oldTS - 1, this.pageSize, true);
207
167
  this.isLoading = false;
168
+ this.isLoadingTop = false;
208
169
  // no more old messages to show, we are at the top of the page
209
170
  if (!data.length)
210
171
  return;
211
172
  // add old data to the end of the array
212
173
  this.pages.push(data);
213
174
  // clear old pages when we reach the limit
214
- if (this.pages.length > this.pagesAllowed) {
215
- this.pages[this.pages.length - this.pagesAllowed - 1] = [];
216
- /**
217
- * find last non empty page in range (this.pages.length, this.firstEmptyIndex)
218
- * we are doing this because any of the middle pages in the currently rendered pages
219
- * could be empty as we allow deleting messages.
220
- * This helps us set the first empty index correctly.
221
- */
222
- for (let i = this.firstEmptyIndex + 1; i < this.pages.length; i++) {
223
- if (this.pages[i].length > 0)
224
- break;
225
- this.firstEmptyIndex = i;
226
- }
227
- }
228
- // update the old timestamp
175
+ if (this.pages.length > this.pagesAllowed)
176
+ this.pages.shift();
177
+ // update timestamps
229
178
  const lastPage = this.pages[this.pages.length - 1];
230
179
  this.oldTS = lastPage[lastPage.length - 1].timeMs;
231
- // update the new timestamp
232
- this.newTS = this.pages[this.firstEmptyIndex + 1][0].timeMs;
180
+ this.newTS = this.pages[0][0].timeMs;
233
181
  this.rerender();
182
+ // fix scroll position
183
+ if (scrollAnchor)
184
+ this.pendingScrollAnchor = scrollAnchor;
234
185
  }
235
186
  async loadNextPage() {
236
187
  if (this.isLoading)
237
188
  return;
238
- // new timestamp needs to be assigned by loadPrevPage method
189
+ // Do nothing. New timestamp needs to be assigned by loadPrevPage method
239
190
  if (!this.newTS) {
240
191
  this.showNewMessagesCTR = false;
241
192
  this.shouldScrollToBottom = false;
242
193
  return;
243
194
  }
244
- // load data
195
+ // for autoscroll or scroll to bottom button
196
+ const maxAutoLoads = 200;
197
+ let loads = 0;
198
+ let prevNewTS = this.newTS;
245
199
  this.isLoading = true;
246
- const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
247
- this.isLoading = false;
248
- // no more new messages to load
249
- if (!data.length) {
250
- this.showNewMessagesCTR = false;
251
- this.shouldScrollToBottom = false;
252
- // remove extra pages from the start if any (could be due to users deleting messages)
253
- this.pages = this.pages.filter((page) => page.length > 0);
254
- this.firstEmptyIndex = -1;
255
- return;
256
- }
257
- // when filling empty pages
258
- if (this.firstEmptyIndex > -1) {
259
- this.pages[this.firstEmptyIndex] = data.reverse();
260
- }
261
- else {
262
- // when already at the bottom and loading messages in realtime
200
+ this.isLoadingBottom = true;
201
+ while (loads < maxAutoLoads) {
202
+ const scrollAnchor = this.getScrollAnchor('bottom');
203
+ const data = await this.fetchData(this.newTS + 1, this.pageSize, false);
204
+ this.isLoading = false;
205
+ this.isLoadingBottom = false;
206
+ // no more new messages to load
207
+ if (!data.length) {
208
+ this.showNewMessagesCTR = false;
209
+ this.shouldScrollToBottom = false;
210
+ break;
211
+ }
212
+ // load new messages and append to the start
263
213
  this.pages.unshift(data.reverse());
214
+ // remove pages if out of bounds
215
+ if (this.pages.length > this.pagesAllowed)
216
+ this.pages.pop();
217
+ // update timestamps
218
+ const lastPage = this.pages[this.pages.length - 1];
219
+ this.oldTS = lastPage[lastPage.length - 1].timeMs;
220
+ this.newTS = this.pages[0][0].timeMs;
221
+ this.rerender();
222
+ this.pendingScrollAnchor = scrollAnchor;
223
+ if (!this.shouldScrollToBottom)
224
+ break;
225
+ // if should scroll to bottom then retrigger
226
+ await this.waitForNextFrame();
227
+ this.scrollToBottom();
228
+ await this.waitForNextFrame();
229
+ // if no new messages, break
230
+ if (this.newTS === prevNewTS)
231
+ break;
232
+ prevNewTS = this.newTS;
233
+ loads++;
264
234
  }
265
- if (this.pages.length > this.pagesAllowed) {
266
- this.pages.pop();
235
+ }
236
+ // Find the element that is closest to the top/bottom of the container
237
+ getScrollAnchor(edge = 'top') {
238
+ if (!this.$containerRef)
239
+ return null;
240
+ const containerRect = this.$containerRef.getBoundingClientRect();
241
+ const candidates = Array.from(this.$containerRef.querySelectorAll('[id]')).filter((el) => el.id !== 'top-scroll' && el.id !== 'bottom-scroll');
242
+ let best = null;
243
+ for (const el of candidates) {
244
+ const rect = el.getBoundingClientRect();
245
+ const isVisibleInContainer = rect.bottom > containerRect.top && rect.top < containerRect.bottom;
246
+ if (!isVisibleInContainer)
247
+ continue;
248
+ if (edge === 'top') {
249
+ const offsetTop = rect.top - containerRect.top;
250
+ if (best == null || (best.edge === 'top' && offsetTop < best.offsetTop)) {
251
+ best = { id: el.id, edge: 'top', offsetTop };
252
+ }
253
+ }
254
+ else {
255
+ const offsetBottom = containerRect.bottom - rect.bottom;
256
+ if (best == null || (best.edge === 'bottom' && offsetBottom < best.offsetBottom)) {
257
+ best = { id: el.id, edge: 'bottom', offsetBottom };
258
+ }
259
+ }
267
260
  }
268
- // smallest value for firstEmptyIndex can be -1, so we cap the index at 0
269
- this.newTS = this.pages[Math.max(0, this.firstEmptyIndex)][0].timeMs;
270
- // remove all empty pages from the end
271
- for (let i = this.pages.length - 1; i > this.firstEmptyIndex; i--) {
272
- if (this.pages[i].length > 0)
273
- break;
274
- // if page is empty, remove it
275
- this.pages.pop();
261
+ return best;
262
+ }
263
+ //instant scroll to anchor to make sure we are at the same position after a rerender
264
+ restoreScrollToAnchor(anchor) {
265
+ if (!this.$containerRef)
266
+ return;
267
+ // make element id safe to use inside a CSS selector
268
+ const escapeId = (id) => {
269
+ var _a;
270
+ const cssEscape = (_a = globalThis.CSS) === null || _a === void 0 ? void 0 : _a.escape;
271
+ return typeof cssEscape === 'function'
272
+ ? cssEscape(id)
273
+ : id.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
274
+ };
275
+ const el = this.$containerRef.querySelector(`#${escapeId(anchor.id)}`);
276
+ if (!el)
277
+ return;
278
+ const containerRect = this.$containerRef.getBoundingClientRect();
279
+ const rect = el.getBoundingClientRect();
280
+ if (anchor.edge === 'top') {
281
+ const newOffsetTop = rect.top - containerRect.top;
282
+ this.$containerRef.scrollTop += newOffsetTop - anchor.offsetTop;
276
283
  }
277
- // update the old timestamp
278
- const lastPage = this.pages[this.pages.length - 1];
279
- this.oldTS = lastPage[lastPage.length - 1].timeMs;
280
- // when scrolling too fast scroll a bit to the top to be able to load new messages when you scroll down
281
- if (this.$containerRef.scrollTop === 0)
282
- this.$containerRef.scrollTop = -60;
283
- // smallest value for this index can be -1 (indicates we are at the bottom of the page).
284
- this.firstEmptyIndex = Math.max(-1, this.firstEmptyIndex - 1);
285
- this.rerender();
284
+ else {
285
+ const newOffsetBottom = containerRect.bottom - rect.bottom;
286
+ this.$containerRef.scrollTop += anchor.offsetBottom - newOffsetBottom;
287
+ }
288
+ }
289
+ // this method is called recursively based on shouldScrollToBottom (see loadNextPage)
290
+ scrollToBottom() {
291
+ this.$bottomRef.scrollIntoView({ behavior: 'smooth' });
292
+ }
293
+ waitForNextFrame() {
294
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
295
+ }
296
+ rerender() {
297
+ this.rerenderBoolean = !this.rerenderBoolean;
286
298
  }
287
299
  render() {
288
300
  /**
289
- * div.container is flex=column-reverse
290
- * which is why div#bottom-scroll comes before div#top-scroll
301
+ * div.container is flex=column-reversewhich is why div#bottom-scroll comes before div#top-scroll
291
302
  */
292
- 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: () => {
303
+ 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: () => {
293
304
  this.shouldScrollToBottom = true;
294
305
  this.scrollToBottom();
295
- } }, 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) }))));
306
+ } }, 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) }))));
296
307
  }
297
308
  static get is() { return "rtk-paginated-list"; }
298
309
  static get encapsulation() { return "shadow"; }
@@ -7,7 +7,7 @@ import { d as defineCustomElement$m } from './p-241a8245.js';
7
7
  import { d as defineCustomElement$l } from './p-1391bef0.js';
8
8
  import { d as defineCustomElement$k } from './p-a73665b4.js';
9
9
  import { d as defineCustomElement$j } from './p-28170a8d.js';
10
- import { d as defineCustomElement$i } from './p-ae376177.js';
10
+ import { d as defineCustomElement$i } from './p-7be71567.js';
11
11
  import { d as defineCustomElement$h } from './p-1f5a4682.js';
12
12
  import { d as defineCustomElement$g } from './p-598dc3f2.js';
13
13
  import { d as defineCustomElement$f } from './p-0e5cc539.js';
@@ -20,7 +20,7 @@ import { d as defineCustomElement$9 } from './p-2447a26f.js';
20
20
  import { d as defineCustomElement$8 } from './p-819cb785.js';
21
21
  import { d as defineCustomElement$7 } from './p-7148ec6a.js';
22
22
  import { d as defineCustomElement$6 } from './p-0f2de0f8.js';
23
- import { d as defineCustomElement$5 } from './p-0d472019.js';
23
+ import { d as defineCustomElement$5 } from './p-7f8d9afc.js';
24
24
  import { d as defineCustomElement$4 } from './p-4ebf9684.js';
25
25
  import { d as defineCustomElement$3 } from './p-4902c5cf.js';
26
26
  import { d as defineCustomElement$2 } from './p-6739a399.js';
@@ -11,7 +11,7 @@ import { d as defineCustomElement$7 } from './p-2447a26f.js';
11
11
  import { d as defineCustomElement$6 } from './p-819cb785.js';
12
12
  import { d as defineCustomElement$5 } from './p-7148ec6a.js';
13
13
  import { d as defineCustomElement$4 } from './p-0f2de0f8.js';
14
- import { d as defineCustomElement$3 } from './p-0d472019.js';
14
+ import { d as defineCustomElement$3 } from './p-7f8d9afc.js';
15
15
  import { d as defineCustomElement$2 } from './p-4ebf9684.js';
16
16
  import { d as defineCustomElement$1 } from './p-6739a399.js';
17
17
 
@@ -142,7 +142,7 @@ const RtkChatMessagesUiPaginated = /*@__PURE__*/ proxyCustomElement(class RtkCha
142
142
  }
143
143
  const isSelf = message.userId === this.meeting.self.userId;
144
144
  const viewType = isSelf ? 'outgoing' : 'incoming';
145
- return (h("div", null, h("div", { class: "message-wrapper" }, h("rtk-message-view", { pinned: message.pinned, time: message.time, actions: this.getMessageActions(message), authorName: message.displayName, isSelf: isSelf, avatarUrl: displayPicture, hideAuthorName: isContinued, viewType: viewType, variant: "bubble", onAction: (event) => this.onMessageActionHandler(event.detail, message) }, h("div", null, h("div", { class: "body" }, message.type === 'text' && (h("rtk-text-message-view", { text: message.message, isMarkdown: true })), message.type === 'file' && (h("rtk-file-message-view", { name: message.name, url: message.link, size: message.size })), message.type === 'image' && (h("rtk-image-message-view", { url: message.link, onPreview: () => {
145
+ return (h("div", null, h("div", { class: "message-wrapper", id: message.id }, h("rtk-message-view", { pinned: message.pinned, time: message.time, actions: this.getMessageActions(message), authorName: message.displayName, isSelf: isSelf, avatarUrl: displayPicture, hideAuthorName: isContinued, viewType: viewType, variant: "bubble", onAction: (event) => this.onMessageActionHandler(event.detail, message) }, h("div", null, h("div", { class: "body" }, message.type === 'text' && (h("rtk-text-message-view", { text: message.message, isMarkdown: true })), message.type === 'file' && (h("rtk-file-message-view", { name: message.name, url: message.link, size: message.size })), message.type === 'image' && (h("rtk-image-message-view", { url: message.link, onPreview: () => {
146
146
  this.stateUpdate.emit({ image: message });
147
147
  } }))))))));
148
148
  };
@@ -190,7 +190,7 @@ const RtkChatMessagesUiPaginated = /*@__PURE__*/ proxyCustomElement(class RtkCha
190
190
  this.lastReadMessageIndex = -1;
191
191
  }
192
192
  render() {
193
- return (h(Host, { key: '012b7189dfbdccfd8017cc9023263e6a7e9afd44' }, h("rtk-paginated-list", { key: '0ea37ee880fda0acdd7460b6da5f03e11ac304bf', ref: (el) => (this.$paginatedListRef = el), pageSize: this.pageSize, pagesAllowed: 3, fetchData: this.getChatMessages, createNodes: this.createChatNodes, selectedItemId: this.selectedChannelId, emptyListLabel: this.t('chat.empty_channel') }, h("slot", { key: '53cb197b6d9319f470e87fe73d7ca0d158778e3f' }))));
193
+ return (h(Host, { key: 'c710da6e2fda420146905a2ed75d3444dd6d2c0b' }, h("rtk-paginated-list", { key: '51a36437e38e9c0242cca34bfda39f6d8309bee3', ref: (el) => (this.$paginatedListRef = el), pageSize: this.pageSize, pagesAllowed: 3, fetchData: this.getChatMessages, createNodes: this.createChatNodes, selectedItemId: this.selectedChannelId, emptyListLabel: this.t('chat.empty_channel') }, h("slot", { key: '69b54a41263510b425ce3e39af055321c4e2deb8' }))));
194
194
  }
195
195
  get host() { return this; }
196
196
  static get watchers() { return {