@webitel/ui-chats 0.1.40 → 0.1.42

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 (50) hide show
  1. package/package.json +17 -1
  2. package/src/adapters/chat-web-sdk/toChatMessage.ts +111 -0
  3. package/src/adapters/index.ts +12 -0
  4. package/src/types/index.ts +15 -0
  5. package/src/ui/index.ts +2 -0
  6. package/src/ui/messaging/components/chat-date-divider.vue +1 -1
  7. package/src/ui/messaging/components/scroll-to-bottom-btn.vue +0 -2
  8. package/src/ui/messaging/components/the-messages-container.vue +56 -46
  9. package/src/ui/messaging/composables/useChatScroll.ts +185 -75
  10. package/src/ui/messaging/composables/useObserveHeightUntilStable.ts +75 -0
  11. package/src/ui/messaging/composables/useScrollToBottomBtn.ts +51 -0
  12. package/src/ui/messaging/modules/message/components/chat-message.vue +1 -2
  13. package/src/ui/messaging/modules/message/components/details/chat-message-document.vue +1 -2
  14. package/src/ui/messaging/modules/message/components/details/chat-message-image.vue +0 -2
  15. package/src/ui/messaging/modules/message/components/details/chat-message-player.vue +1 -2
  16. package/src/ui/messaging/modules/message/components/details/chat-message-size-exceeded-error.vue +1 -1
  17. package/src/ui/messaging/modules/message/components/details/chat-message-time.vue +1 -1
  18. package/src/ui/messaging/types/ChatMessage.types.ts +3 -2
  19. package/src/ui/the-chat-container.vue +7 -0
  20. package/src/ui/utils/ResultCallbacks.types.ts +1 -1
  21. package/src/ui/utils/emitter.ts +1 -1
  22. package/types/adapters/chat-web-sdk/toChatMessage.d.ts +23 -0
  23. package/types/adapters/index.d.ts +8 -0
  24. package/types/types/index.d.ts +8 -0
  25. package/types/ui/chat-footer/modules/user-input/enums/ChatAction.enum.d.ts +6 -5
  26. package/types/ui/index.d.ts +7 -3
  27. package/types/ui/messaging/components/the-messages-container.vue.d.ts +5 -1
  28. package/types/ui/messaging/composables/useChatScroll.d.ts +13 -3
  29. package/types/ui/messaging/composables/useObserveHeightUntilStable.d.ts +11 -0
  30. package/types/ui/messaging/composables/useScrollToBottomBtn.d.ts +9 -0
  31. package/types/ui/messaging/modules/message/composables/useChatMessageFile.d.ts +2 -0
  32. package/types/ui/messaging/types/ChatMessage.types.d.ts +33 -32
  33. package/types/ui/the-chat-container.vue.d.ts +39 -49
  34. package/types/ui/utils/ResultCallbacks.types.d.ts +1 -1
  35. package/types/ui/utils/emitter.d.ts +1 -1
  36. package/types/ui/chat-container.vue.d.ts +0 -76
  37. package/types/ui/chat-footer/modules/user-input/types/ChatAction.types.d.ts +0 -10
  38. package/types/ui/chat-input/components/actions/attach-files-action.vue.d.ts +0 -29
  39. package/types/ui/chat-input/components/actions/emoji-picker-action.vue.d.ts +0 -24
  40. package/types/ui/chat-input/components/actions/send-message-action.vue.d.ts +0 -29
  41. package/types/ui/chat-input/components/chat-input-actions-bar.vue.d.ts +0 -43
  42. package/types/ui/chat-input/components/chat-input-actions-wrapper.vue.d.ts +0 -34
  43. package/types/ui/chat-input/components/chat-input.vue.d.ts +0 -42
  44. package/types/ui/chat-input/components/chat-text-field.vue.d.ts +0 -32
  45. package/types/ui/chat-input/enums/ChatAction.enum.d.ts +0 -6
  46. package/types/ui/media-viewer/media-viewer.vue.d.ts +0 -11
  47. package/types/ui/messaging/components/chat-messages-container.vue.d.ts +0 -11
  48. package/types/ui/messaging/components/the-chat-messages-container.vue.d.ts +0 -40
  49. package/types/ui/messaging/composebles/useChatScroll.d.ts +0 -12
  50. package/types/ui/messaging/modules/message/components/details/chat-message-avatar.vue.d.ts +0 -10
@@ -1,8 +1,9 @@
1
- import { useScroll } from '@vueuse/core';
1
+ import { useResizeObserver, useScroll } from '@vueuse/core';
2
2
  import {
3
3
  type ComputedRef,
4
4
  computed,
5
5
  nextTick,
6
+ onUnmounted,
6
7
  type Ref,
7
8
  ref,
8
9
  watch,
@@ -10,105 +11,180 @@ import {
10
11
 
11
12
  import type { ChatMessageType } from '../types/ChatMessage.types';
12
13
 
13
- export const useChatScroll = (
14
- element: Ref<HTMLElement | null> = null,
15
- messages: Ref<ChatMessageType[]> | ComputedRef<ChatMessageType[]>,
16
- isLoading?: Ref<boolean> | ComputedRef<boolean>,
17
- ) => {
18
- /* @author ye.pohranichna
19
- why 136px? because: https://webitel.atlassian.net/browse/WTEL-7136 */
20
- const defaultThreshold = 136;
21
- const { arrivedState } = useScroll(element);
22
-
23
- const newUnseenMessagesCount = ref<number>(0);
24
- const showScrollToBottomBtn = ref<boolean>(false);
25
- /* @author ye.pohranichna
26
- the distance where the scrollToBottomBtn must be shown/hide. */
27
- const threshold = ref<number>(defaultThreshold);
28
- const isLoadingNextMessages = ref<boolean>(false);
29
- const lastVisibleMessageEl = ref<HTMLElement | null>(null);
30
-
31
- const isLastMessageIsMy = computed<boolean>(
32
- () => lastMessage.value?.member?.self,
33
- );
34
- const lastMessage = computed<ChatMessageType>(() => messages.value?.at(-1));
35
- const handleChatScroll = () => {
36
- const wrapper = element.value;
37
- if (!wrapper) return;
38
-
39
- handleShowScrollToBottomBtn(wrapper);
40
- };
14
+ import { useScrollToBottomBtn } from './useScrollToBottomBtn';
41
15
 
42
- const handleChatResize = () => {
43
- const wrapper = element.value;
44
- if (!wrapper) return;
16
+ export interface UseChatScrollOptions {
17
+ chatContainer: Ref<HTMLElement | null>;
18
+ chatContent: Ref<HTMLElement | null>;
19
+ messages: Ref<ChatMessageType[]> | ComputedRef<ChatMessageType[]>;
20
+ chatId: ComputedRef<string>;
21
+ isChatClosed: ComputedRef<boolean>;
22
+ isLoading?: Ref<boolean> | ComputedRef<boolean>;
23
+ onBeforeStart?: (options: {
24
+ scrollToBottom: (behavior?: ScrollBehavior) => void;
25
+ }) => void;
26
+ }
45
27
 
46
- updateThreshold(wrapper);
47
- handleShowScrollToBottomBtn(wrapper);
48
- };
28
+ export const useChatScroll = ({
29
+ chatContainer,
30
+ chatContent,
31
+ messages,
32
+ chatId,
33
+ isChatClosed,
34
+ isLoading,
35
+ onBeforeStart = (options) => {
36
+ options.scrollToBottom();
37
+ },
38
+ }: UseChatScrollOptions) => {
39
+ const { arrivedState } = useScroll(chatContainer);
49
40
 
50
- const handleShowScrollToBottomBtn = (el: HTMLElement) => {
51
- if (arrivedState.bottom) {
52
- resetScrollToBottomBtn();
53
- return;
54
- /* @author ye.pohranichna
55
- quit the function because we are already at the bottom */
56
- }
41
+ const {
42
+ showScrollToBottomBtn,
43
+ handleChatScroll,
44
+ resetScrollToBottomBtn,
45
+ updateScrollToBottomBtnVisibility,
46
+ updateThreshold,
47
+ } = useScrollToBottomBtn(chatContainer, arrivedState);
57
48
 
58
- const { scrollTop, scrollHeight, clientHeight } = el;
59
- const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
60
- showScrollToBottomBtn.value = distanceFromBottom > threshold.value;
61
- };
49
+ let isLoadingNextMessages = false;
50
+ let lastVisibleMessageEl: HTMLElement | null = null;
51
+ let prevScrollHeight = 0;
52
+ let resizeObserver: ResizeObserver | null = null;
53
+ let isViewportTransition = false;
54
+ let viewportTransitionTimer: ReturnType<typeof setTimeout> | null = null;
55
+ /**
56
+ * @author PolinaSukhorukova-webitel
57
+ *
58
+ * 2 tolerates subpixel scrollTop/clientHeight rounding that can leave
59
+ * their sum a fraction short of scrollHeight at the very bottom.
60
+ */
61
+ const bottomThreshold = 2;
62
62
 
63
- const resetScrollToBottomBtn = (): void => {
64
- newUnseenMessagesCount.value = 0;
65
- showScrollToBottomBtn.value = false;
66
- };
63
+ const newUnseenMessagesCount = ref(0);
67
64
 
68
- const updateThreshold = (el: HTMLElement) => {
69
- /* @author ye.pohranichna
70
- need to update if clientHeight was changed
71
- https://webitel.atlassian.net/browse/WTEL-7136 */
72
- threshold.value = Math.max(defaultThreshold, el.clientHeight * 0.3);
73
- };
65
+ const lastMessage = computed(() => messages.value?.at(-1));
66
+ const isLastMessageMy = computed(() => !!lastMessage.value?.member?.self);
74
67
 
75
68
  const handleBtnAfterNewMessage = () => {
76
- if (arrivedState.bottom || isLastMessageIsMy.value) {
77
- scrollToBottom('smooth');
78
- } else {
69
+ if (isLastMessageMy.value) {
70
+ scrollToBottom('instant');
71
+ } else if (!arrivedState.bottom) {
79
72
  newUnseenMessagesCount.value += 1;
80
73
  }
81
74
  };
82
75
 
83
76
  const scrollToBottom = (behavior: ScrollBehavior = 'instant') => {
84
- element?.value.scrollTo({
85
- top: element?.value.scrollHeight,
86
- behavior: behavior === 'instant' ? 'auto' : behavior,
77
+ chatContainer.value?.scrollTo({
78
+ top: chatContainer.value?.scrollHeight,
79
+ behavior,
87
80
  });
88
81
 
89
82
  newUnseenMessagesCount.value = 0;
90
- showScrollToBottomBtn.value = false;
91
- };
92
83
 
84
+ resetScrollToBottomBtn();
85
+ };
93
86
  const getTopMessageEl = () => {
94
87
  // help to fix chat viewing position when new messages was loaded
95
- if (!element.value?.children) return;
96
- lastVisibleMessageEl.value =
97
- element.value?.getElementsByClassName('chat-message')[0] ?? null;
88
+ if (!chatContainer.value?.children) return;
89
+ lastVisibleMessageEl =
90
+ (chatContainer.value?.getElementsByClassName(
91
+ 'chat-message',
92
+ )[0] as HTMLElement) ?? null;
98
93
  };
99
94
 
100
95
  const loadNextMessages = (
101
- canLoadMore: boolean | undefined,
96
+ canLoadMore: boolean,
102
97
  onLoadNextMessages: () => void,
103
98
  ) => {
104
- if (isLoadingNextMessages.value || isLoading?.value || !canLoadMore) return;
99
+ if (isLoadingNextMessages || isLoading?.value || !canLoadMore) return;
105
100
 
106
- isLoadingNextMessages.value = true;
101
+ isLoadingNextMessages = true;
107
102
  getTopMessageEl();
108
103
 
109
104
  onLoadNextMessages();
110
105
  };
111
106
 
107
+ const startStickToBottomObserving = () => {
108
+ if (!chatContent.value) return;
109
+
110
+ prevScrollHeight = chatContainer.value?.scrollHeight ?? 0;
111
+
112
+ resizeObserver = new ResizeObserver(() => {
113
+ const el = chatContainer.value;
114
+ if (!el) return;
115
+
116
+ const newScrollHeight = el.scrollHeight;
117
+
118
+ if (isViewportTransition) {
119
+ prevScrollHeight = newScrollHeight;
120
+ updateScrollToBottomBtnVisibility(el);
121
+ return;
122
+ }
123
+
124
+ const wasNearBottom =
125
+ el.scrollTop + el.clientHeight >= prevScrollHeight - bottomThreshold;
126
+ const contentGrown = newScrollHeight > prevScrollHeight;
127
+
128
+ /**
129
+ * @author PolinaSukhorukova-webitel
130
+ *
131
+ * arrivedState.bottom lags after programmatic scrollTo,
132
+ * so the distance is also checked manually.
133
+ */
134
+ const shouldScrollToBottom =
135
+ contentGrown && (arrivedState.bottom || wasNearBottom);
136
+
137
+ if (shouldScrollToBottom) {
138
+ scrollToBottom('instant');
139
+ }
140
+
141
+ prevScrollHeight = newScrollHeight;
142
+ updateScrollToBottomBtnVisibility(el);
143
+ });
144
+
145
+ resizeObserver.observe(chatContent.value);
146
+ };
147
+
148
+ const stopStickToBottomObserving = () => {
149
+ resizeObserver?.disconnect();
150
+ resizeObserver = null;
151
+ };
152
+
153
+ useResizeObserver(chatContainer, () => {
154
+ const el = chatContainer.value;
155
+ if (!el) return;
156
+
157
+ updateThreshold(el.clientHeight);
158
+ updateScrollToBottomBtnVisibility(el);
159
+ });
160
+
161
+ const resetScrollState = () => {
162
+ stopStickToBottomObserving();
163
+ prevScrollHeight = 0;
164
+ newUnseenMessagesCount.value = 0;
165
+ resetScrollToBottomBtn();
166
+ };
167
+
168
+ const onFullscreenChange = () => {
169
+ isViewportTransition = true;
170
+
171
+ if (viewportTransitionTimer) clearTimeout(viewportTransitionTimer);
172
+
173
+ viewportTransitionTimer = setTimeout(() => {
174
+ isViewportTransition = false;
175
+ viewportTransitionTimer = null;
176
+ }, 500);
177
+ };
178
+
179
+ /**
180
+ * @author PolinaSukhorukova-webitel
181
+ *
182
+ * WTEL-9968 (https://webitel.atlassian.net/browse/WTEL-9968)
183
+ * to avoid extra bottom scroll when chat is in fullscreen
184
+ */
185
+
186
+ document.addEventListener('fullscreenchange', onFullscreenChange);
187
+
112
188
  watch(
113
189
  () => messages.value?.length,
114
190
  (newValue, oldValue) => {
@@ -123,25 +199,59 @@ export const useChatScroll = (
123
199
  watch(
124
200
  () => isLoading?.value,
125
201
  async (loading) => {
126
- if (loading || !isLoadingNextMessages.value) return;
202
+ // restore scroll position after older messages are prepended:
203
+ // scroll to the previously top message so the view doesn't jump
204
+ if (loading || !isLoadingNextMessages) return;
127
205
 
128
206
  await nextTick();
129
207
 
130
- element.value?.scrollTo({
131
- top: lastVisibleMessageEl.value?.offsetTop,
208
+ chatContainer.value?.scrollTo({
209
+ top: lastVisibleMessageEl?.offsetTop,
132
210
  behavior: 'auto',
133
211
  });
134
212
 
135
- isLoadingNextMessages.value = false;
213
+ isLoadingNextMessages = false;
136
214
  },
137
215
  );
138
216
 
217
+ watch(
218
+ () => chatId.value,
219
+ async () => {
220
+ resetScrollState();
221
+
222
+ if (isChatClosed.value) return;
223
+
224
+ await nextTick();
225
+
226
+ onBeforeStart?.({
227
+ scrollToBottom,
228
+ });
229
+
230
+ startStickToBottomObserving();
231
+ },
232
+ {
233
+ immediate: true,
234
+ },
235
+ );
236
+
237
+ watch(
238
+ () => arrivedState.bottom,
239
+ (bottom) => {
240
+ if (bottom) newUnseenMessagesCount.value = 0;
241
+ },
242
+ );
243
+
244
+ onUnmounted(() => {
245
+ stopStickToBottomObserving();
246
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
247
+ if (viewportTransitionTimer) clearTimeout(viewportTransitionTimer);
248
+ });
249
+
139
250
  return {
140
251
  showScrollToBottomBtn,
141
252
  newUnseenMessagesCount,
142
253
  scrollToBottom,
143
254
  loadNextMessages,
144
255
  handleChatScroll,
145
- handleChatResize,
146
256
  };
147
257
  };
@@ -0,0 +1,75 @@
1
+ import { onUnmounted, type Ref } from 'vue';
2
+
3
+ /**
4
+ * @author PolinaSukhorukova-webitel
5
+ *
6
+ * Fires the callback on every resize and disconnects itself
7
+ * once clientHeight is unchanged twice in a row.
8
+ */
9
+
10
+ export const useObserveHeightUntilStable = (
11
+ chatContainer: Ref<HTMLElement | null>,
12
+ callback: () => void,
13
+ ) => {
14
+ let observer: ResizeObserver | null = null;
15
+ let isViewportTransition = false;
16
+ let viewportTransitionTimer: ReturnType<typeof setTimeout> | null = null;
17
+
18
+ const onFullscreenChange = () => {
19
+ isViewportTransition = true;
20
+
21
+ if (viewportTransitionTimer) clearTimeout(viewportTransitionTimer);
22
+
23
+ viewportTransitionTimer = setTimeout(() => {
24
+ isViewportTransition = false;
25
+ viewportTransitionTimer = null;
26
+ }, 500);
27
+ };
28
+
29
+ const stopObserve = () => {
30
+ observer?.disconnect();
31
+ observer = null;
32
+ };
33
+
34
+ const startObserve = () => {
35
+ if (!chatContainer.value) return;
36
+
37
+ let lastClientHeight = chatContainer.value.clientHeight;
38
+ let stableCount = 0;
39
+
40
+ observer = new ResizeObserver(() => {
41
+ const currentClientHeight = chatContainer.value?.clientHeight;
42
+
43
+ if (!isViewportTransition) callback();
44
+
45
+ if (currentClientHeight === lastClientHeight) {
46
+ stableCount++;
47
+ if (stableCount >= 2) stopObserve();
48
+ } else {
49
+ stableCount = 0;
50
+ lastClientHeight = currentClientHeight;
51
+ }
52
+ });
53
+
54
+ observer.observe(chatContainer.value);
55
+ };
56
+
57
+ /**
58
+ * @author PolinaSukhorukova-webitel
59
+ *
60
+ * WTEL-9968 (https://webitel.atlassian.net/browse/WTEL-9968)
61
+ * to avoid extra bottom scroll when chat is in fullscreen
62
+ */
63
+ document.addEventListener('fullscreenchange', onFullscreenChange);
64
+
65
+ onUnmounted(() => {
66
+ stopObserve();
67
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
68
+ if (viewportTransitionTimer) clearTimeout(viewportTransitionTimer);
69
+ });
70
+
71
+ return {
72
+ startObserve,
73
+ stopObserve,
74
+ };
75
+ };
@@ -0,0 +1,51 @@
1
+ import type { UseScrollReturn } from '@vueuse/core';
2
+ import { type Ref, ref } from 'vue';
3
+
4
+ export const useScrollToBottomBtn = (
5
+ chatContainer: Ref<HTMLElement | null>,
6
+ arrivedState: UseScrollReturn['arrivedState'],
7
+ ) => {
8
+ const showScrollToBottomBtn = ref(false);
9
+ /* @author ye.pohranichna
10
+ why 136px? because: https://webitel.atlassian.net/browse/WTEL-7136 */
11
+ const defaultThreshold = 136;
12
+ /* @author ye.pohranichna
13
+ the distance where the scrollToBottomBtn must be shown/hide. */
14
+ const threshold = ref(defaultThreshold);
15
+
16
+ const handleChatScroll = () => {
17
+ const wrapper = chatContainer.value;
18
+ if (!wrapper) return;
19
+
20
+ updateScrollToBottomBtnVisibility(wrapper);
21
+ };
22
+
23
+ const resetScrollToBottomBtn = () => {
24
+ showScrollToBottomBtn.value = false;
25
+ };
26
+
27
+ const updateScrollToBottomBtnVisibility = (el: HTMLElement) => {
28
+ if (arrivedState.bottom) {
29
+ resetScrollToBottomBtn();
30
+ return;
31
+ /* @author ye.pohranichna
32
+ quit the function because we are already at the bottom */
33
+ }
34
+
35
+ const { scrollTop, scrollHeight, clientHeight } = el;
36
+ const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
37
+ showScrollToBottomBtn.value = distanceFromBottom > threshold.value;
38
+ };
39
+
40
+ const updateThreshold = (clientHeight: number) => {
41
+ threshold.value = Math.max(defaultThreshold, clientHeight * 0.3);
42
+ };
43
+
44
+ return {
45
+ showScrollToBottomBtn,
46
+ handleChatScroll,
47
+ resetScrollToBottomBtn,
48
+ updateScrollToBottomBtnVisibility,
49
+ updateThreshold,
50
+ };
51
+ };
@@ -72,8 +72,7 @@
72
72
 
73
73
  <script setup lang="ts">
74
74
  import { ComponentSize } from '@webitel/ui-sdk/enums';
75
- import { computed, defineEmits, defineProps, inject } from 'vue';
76
-
75
+ import { computed, inject } from 'vue';
77
76
  import type { ChatMessageType } from '../../../types/ChatMessage.types';
78
77
  import { useChatMessageFile } from '../composables/useChatMessageFile';
79
78
  import { MessageAction } from '../enums/MessageAction.enum';
@@ -20,8 +20,7 @@
20
20
 
21
21
  <script setup lang="ts">
22
22
  import { prettifyFileSize } from '@webitel/ui-sdk/scripts';
23
- import { computed, defineProps } from 'vue';
24
-
23
+ import { computed } from 'vue';
25
24
  import type { ChatMessageFile } from '../../../../types/ChatMessage.types';
26
25
 
27
26
  const props = withDefaults(
@@ -17,8 +17,6 @@
17
17
  setup
18
18
  lang="ts"
19
19
  >
20
- import { defineEmits, defineProps } from 'vue';
21
-
22
20
  import type { ChatMessageFile } from '../../../../types/ChatMessage.types';
23
21
 
24
22
  const props = defineProps<{
@@ -30,8 +30,7 @@
30
30
  <script setup lang="ts">
31
31
  import { WtPlayer, WtVidstackPlayer } from '@webitel/ui-sdk/components';
32
32
  import { ComponentSize } from '@webitel/ui-sdk/enums';
33
- import { computed, defineEmits, defineProps } from 'vue';
34
-
33
+ import { computed } from 'vue';
35
34
  import type { ChatMessageFile } from '../../../../types/ChatMessage.types';
36
35
 
37
36
  const props = defineProps<{
@@ -24,7 +24,7 @@ interface IChatMessageSizeExceededErrorProps {
24
24
  selfSide?: boolean;
25
25
  }
26
26
 
27
- const props = withDefaults(defineProps<IChatMessageSizeExceededErrorProps>(), {
27
+ withDefaults(defineProps<IChatMessageSizeExceededErrorProps>(), {
28
28
  selfSide: false,
29
29
  });
30
30
  </script>
@@ -12,7 +12,7 @@
12
12
  lang="ts"
13
13
  >
14
14
  import { prettifyTime } from '@webitel/ui-sdk/scripts';
15
- import { computed, defineProps } from 'vue';
15
+ import { computed } from 'vue';
16
16
 
17
17
  const props = withDefaults(
18
18
  defineProps<{
@@ -1,5 +1,5 @@
1
1
  export interface ChatMessageType {
2
- id: number;
2
+ id: number | string;
3
3
  date?: number;
4
4
  file?: ChatMessageFile;
5
5
  member: ChatMember;
@@ -24,10 +24,11 @@ export type ChatMessageFile = {
24
24
  mime?: string;
25
25
  url?: string;
26
26
  streamUrl?: string;
27
+ malware?: boolean;
27
28
  };
28
29
 
29
30
  export type ChatMember = {
30
- id: number;
31
+ id: number | string;
31
32
  name: string;
32
33
  type: string;
33
34
  userId?: number;
@@ -18,6 +18,8 @@
18
18
  :without-avatars="props.withoutAvatars"
19
19
  :agent-name="props.agentName"
20
20
  :contact="props.contact"
21
+ :chat-id="props.chatId"
22
+ :is-chat-closed="props.isChatClosed"
21
23
  @[ChatAction.LoadNextMessages]="emit(ChatAction.LoadNextMessages)"
22
24
  />
23
25
  </slot>
@@ -83,6 +85,8 @@ const props = withDefaults(
83
85
  readonly?: boolean; // hide chat footer with textarea and action-buttons
84
86
  agentName?: string;
85
87
  contact?: WebitelContactsContact;
88
+ chatId?: string;
89
+ isChatClosed?: boolean;
86
90
  }>(),
87
91
  {
88
92
  size: ComponentSize.MD,
@@ -91,6 +95,8 @@ const props = withDefaults(
91
95
  canLoadNextMessages: false,
92
96
  isNextMessagesLoading: false,
93
97
  readonly: false,
98
+ chatId: '',
99
+ isChatClosed: false,
94
100
  },
95
101
  );
96
102
 
@@ -163,5 +169,6 @@ function sendFile(files: File[]) {
163
169
  flex-direction: column;
164
170
  height: 100%;
165
171
  width: 100%;
172
+ gap: var(--spacing-2xs);
166
173
  }
167
174
  </style>
@@ -1 +1 @@
1
- export { ResultCallbacks } from '@webitel/ui-sdk/src/types';
1
+ export type { ResultCallbacks } from '@webitel/ui-sdk/src/types';
@@ -1,6 +1,6 @@
1
1
  import mitt from 'mitt';
2
2
 
3
- import type { ChatMessageType } from '../../../types/ui';
3
+ import type { ChatMessageType } from '../messaging/types/ChatMessage.types';
4
4
 
5
5
  export type UiChatsEmitterEvents = {
6
6
  insertAtCursor: {
@@ -0,0 +1,23 @@
1
+ import type { IMessage } from '@webitel/chat-web-sdk';
2
+ import type { ChatMessageType } from '../../types';
3
+ export interface ToChatMessageOptions {
4
+ /**
5
+ * Decide whether a message is authored by the current agent / "self" side.
6
+ * Result is written to `ChatMember.self`, which drives outgoing-bubble
7
+ * alignment in the UI. The message alone cannot know who "self" is — that
8
+ * is presentation context the caller owns (e.g. compare `sender.contact`
9
+ * against the logged-in account).
10
+ */
11
+ isSelf?: (message: IMessage) => boolean;
12
+ }
13
+ /**
14
+ * Map a single `@webitel/chat-web-sdk` message (`IMessage`) into the
15
+ * `@webitel/ui-chats` presentation contract (`ChatMessageType`).
16
+ *
17
+ * Anti-corruption layer: keeps `ui-chats` free of any backend/SDK coupling.
18
+ */
19
+ export declare const mapMessageToChatMessage: (message: IMessage, options?: ToChatMessageOptions) => ChatMessageType;
20
+ /**
21
+ * Batch variant of {@link mapMessageToChatMessage} for message history pages.
22
+ */
23
+ export declare const mapMessagesToChatMessages: (messages: readonly IMessage[], options?: ToChatMessageOptions) => ChatMessageType[];
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Adapters mapping external data sources into the `@webitel/ui-chats`
3
+ * presentation contract (`ChatMessageType`, …).
4
+ *
5
+ * Consuming this entry pulls `@webitel/chat-web-sdk` (declared as an optional
6
+ * peerDependency). The core `@webitel/ui-chats/ui` entry stays free of it.
7
+ */
8
+ export { mapMessagesToChatMessages, mapMessageToChatMessage, type ToChatMessageOptions, } from './chat-web-sdk/toChatMessage';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public data contract of `@webitel/ui-chats`.
3
+ *
4
+ * These are the plain, transport-agnostic shapes the UI components accept.
5
+ * External data sources (e.g. `@webitel/chat-web-sdk`) must be mapped into
6
+ * them — see `@webitel/ui-chats/adapters`.
7
+ */
8
+ export type { ChatMember, ChatMessageChatInfo, ChatMessageFile, ChatMessageType, ChatVia, ContactInfo, } from '../ui/messaging/types/ChatMessage.types';
@@ -1,10 +1,11 @@
1
1
  export declare const ChatAction: {
2
- readonly SendMessage: "sendMessage";
3
- readonly AttachFiles: "attachFiles";
4
- readonly EmojiPicker: "emojiPicker";
5
- readonly QuickReplies: "quickReplies";
2
+ readonly SendMessage: "sendMessage";
3
+ readonly AttachFiles: "attachFiles";
4
+ readonly EmojiPicker: "emojiPicker";
5
+ readonly QuickReplies: "quickReplies";
6
+ readonly LoadNextMessages: "loadNextMessages";
6
7
  };
7
8
  export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction];
8
9
  export type SharedActionSlots = {
9
- [key in `action:${ChatAction}`]?: () => any;
10
+ [key in `action:${ChatAction}`]?: () => unknown;
10
11
  };
@@ -1,3 +1,7 @@
1
- export { ChatAction } from "./chat-footer/modules/user-input/enums/ChatAction.enum";
2
- export type { ChatMessageType } from "./messaging/types/ChatMessage.types";
3
- export { default as ChatContainer } from "./the-chat-container.vue";
1
+ export { ChatAction } from './chat-footer/modules/user-input/enums/ChatAction.enum';
2
+ export { useChatScroll } from './messaging/composables/useChatScroll';
3
+ export { useObserveHeightUntilStable } from './messaging/composables/useObserveHeightUntilStable';
4
+ export { useChatMessageFile } from './messaging/modules/message/composables/useChatMessageFile';
5
+ export { MessageAction } from './messaging/modules/message/enums/MessageAction.enum';
6
+ export type { ChatMessageFile, ChatMessageType, } from './messaging/types/ChatMessage.types';
7
+ export { default as ChatContainer } from './the-chat-container.vue';
@@ -7,15 +7,19 @@ type __VLS_Props = {
7
7
  withoutAvatars?: boolean;
8
8
  agentName?: string;
9
9
  contact?: WebitelContactsContact;
10
+ chatId?: string;
11
+ isChatClosed?: boolean;
10
12
  };
11
13
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
12
14
  loadNextMessages: () => any;
13
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
14
16
  onLoadNextMessages?: () => any;
15
17
  }>, {
18
+ chatId: string;
19
+ isChatClosed: boolean;
20
+ isLoading: boolean;
16
21
  withoutAvatars: boolean;
17
22
  next: boolean;
18
- isLoading: boolean;
19
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
24
  declare const _default: typeof __VLS_export;
21
25
  export default _default;