@webitel/ui-chats 0.0.2 → 0.0.4

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 (48) hide show
  1. package/package.json +61 -61
  2. package/src/locale/en/en.ts +8 -8
  3. package/src/ui/chat-container.vue +51 -42
  4. package/src/ui/chat-footer/components/chat-footer-wrapper.vue +2 -4
  5. package/src/ui/chat-footer/modules/user-input/components/actions/attach-files-action.vue +11 -13
  6. package/src/ui/chat-footer/modules/user-input/components/actions/emoji-picker-action.vue +6 -8
  7. package/src/ui/chat-footer/modules/user-input/components/actions/send-message-action.vue +5 -8
  8. package/src/ui/chat-footer/modules/user-input/components/chat-input-actions-bar.vue +34 -37
  9. package/src/ui/chat-footer/modules/user-input/components/chat-text-field.vue +22 -17
  10. package/src/ui/chat-footer/modules/user-input/types/ChatAction.types.ts +5 -5
  11. package/src/ui/index.ts +3 -2
  12. package/src/ui/messaging/components/chat-messages-container.vue +4 -4
  13. package/src/ui/messaging/composebles/useChatScroll.ts +102 -0
  14. package/src/ui/messaging/modules/message/components/chat-message.vue +51 -56
  15. package/src/ui/messaging/modules/message/components/details/chat-message-avatar.vue +21 -22
  16. package/src/ui/messaging/modules/message/components/details/chat-message-blocked-error.vue +1 -1
  17. package/src/ui/messaging/modules/message/components/details/chat-message-document.vue +22 -19
  18. package/src/ui/messaging/modules/message/components/details/chat-message-image.vue +8 -8
  19. package/src/ui/messaging/modules/message/components/details/chat-message-player.vue +14 -12
  20. package/src/ui/messaging/modules/message/components/details/chat-message-text.vue +12 -12
  21. package/src/ui/messaging/modules/message/components/details/chat-message-time.vue +11 -9
  22. package/src/ui/messaging/modules/message/composables/useChatMessageFile.ts +26 -26
  23. package/src/ui/messaging/types/ChatMessage.types.ts +45 -8
  24. package/src/ui/utils/ResultCallbacks.types.ts +1 -1
  25. package/src/ui/utils/emitter.ts +7 -5
  26. package/types/locale/en/en.d.ts +1 -1
  27. package/types/ui/chat-container.vue.d.ts +5 -5
  28. package/types/ui/chat-footer/modules/user-input/components/chat-input-actions-bar.vue.d.ts +1 -2
  29. package/types/ui/chat-footer/modules/user-input/components/chat-text-field.vue.d.ts +2 -2
  30. package/types/ui/chat-input/components/actions/attach-files-action.vue.d.ts +27 -5
  31. package/types/ui/chat-input/components/actions/emoji-picker-action.vue.d.ts +22 -1
  32. package/types/ui/chat-input/components/actions/send-message-action.vue.d.ts +27 -5
  33. package/types/ui/chat-input/components/chat-input-actions-bar.vue.d.ts +34 -12
  34. package/types/ui/chat-input/components/chat-input-actions-wrapper.vue.d.ts +26 -5
  35. package/types/ui/chat-input/components/chat-input.vue.d.ts +32 -10
  36. package/types/ui/chat-input/components/chat-text-field.vue.d.ts +28 -6
  37. package/types/ui/chat-input/enums/ChatAction.enum.d.ts +3 -3
  38. package/types/ui/index.d.ts +3 -2
  39. package/types/ui/messaging/components/chat-messages-container.vue.d.ts +1 -1
  40. package/types/ui/messaging/composebles/useChatScroll.d.ts +9 -0
  41. package/types/ui/messaging/modules/message/components/chat-message.vue.d.ts +5 -4
  42. package/types/ui/messaging/modules/message/components/details/chat-message-avatar.vue.d.ts +1 -1
  43. package/types/ui/messaging/modules/message/components/details/chat-message-document.vue.d.ts +1 -1
  44. package/types/ui/messaging/modules/message/components/details/chat-message-image.vue.d.ts +1 -1
  45. package/types/ui/messaging/modules/message/components/details/chat-message-player.vue.d.ts +3 -3
  46. package/types/ui/messaging/modules/message/composables/useChatMessageFile.d.ts +2 -2
  47. package/types/ui/messaging/types/ChatMessage.types.d.ts +34 -1
  48. package/types/ui/utils/ResultCallbacks.types.d.ts +1 -1
package/package.json CHANGED
@@ -1,63 +1,63 @@
1
1
  {
2
- "name": "@webitel/ui-chats",
3
- "version": "0.0.2",
4
- "description": "Reusable Webitel Frontend Code for Chats UI",
5
- "workspaces": [
6
- "../../",
7
- "../api-services"
8
- ],
9
- "scripts": {
10
- "dev": "vite",
11
- "make-all": "npm version patch --git-tag-version false && (npm run lint:oxlint || true) && (npm run build:types || true) && npm run utils:publish",
12
- "build": "(npm run build:types || true)",
13
- "build:types": "vue-tsc -p ./tsconfig.build.json",
14
- "lint:oxlint": "npx oxlint . --fix --type-aware",
15
- "utils:publish": "npm publish --access public"
16
- },
17
- "dependencies": {
18
- "autolinker": "^4.1.5",
19
- "insert-text-at-cursor": "^0.3.0",
20
- "mitt": "^3.0.1",
21
- "vue": "^3.5.24"
22
- },
23
- "devDependencies": {
24
- "@types/node": "^24.10.0",
25
- "@vitejs/plugin-vue": "^6.0.1",
26
- "@vue/tsconfig": "^0.8.1",
27
- "@webitel/styleguide": "^24.12.61",
28
- "@webitel/ui-sdk": "~25.12",
29
- "oxlint-tsgolint": "^0.5.1",
30
- "typescript": "~5.9.3",
31
- "vite": "npm:rolldown-vite@7.2.2",
32
- "vue-tsc": "^3.1.3"
33
- },
34
- "type": "module",
35
- "main": "index.ts",
36
- "types": "index.d.ts",
37
- "files": [
38
- "src/*",
39
- "types/*",
40
- "Readme.md",
41
- "CHANGELOG.md"
42
- ],
43
- "exports": {
44
- "./ui": {
45
- "types": "./types/ui/index.d.ts",
46
- "import": "./src/ui/index.ts"
47
- }
48
- },
49
- "author": "webitel",
50
- "license": "ISC",
51
- "keywords": [
52
- "webitel",
53
- "webitel-ui"
54
- ],
55
- "repository": {
56
- "type": "git",
57
- "url": "github.com/webitel/webitel-ui-sdk"
58
- },
59
- "engines": {
60
- "npm": ">=10",
61
- "node": ">=v22"
62
- }
2
+ "name": "@webitel/ui-chats",
3
+ "version": "0.0.4",
4
+ "description": "Reusable Webitel Frontend Code for Chats UI",
5
+ "workspaces": [
6
+ "../../",
7
+ "../api-services"
8
+ ],
9
+ "scripts": {
10
+ "dev": "vite",
11
+ "make-all": "npm version patch --git-tag-version false && (npm run lint:biome || true) && (npm run build:types || true) && npm run utils:publish",
12
+ "build": "(npm run build:types || true)",
13
+ "build:types": "vue-tsc -p ./tsconfig.build.json",
14
+ "lint:biome": "biome check --write --unsafe",
15
+ "utils:publish": "npm publish --access public"
16
+ },
17
+ "dependencies": {
18
+ "autolinker": "^4.1.5",
19
+ "insert-text-at-cursor": "^0.3.0",
20
+ "mitt": "^3.0.1",
21
+ "vue": "^3.5.24"
22
+ },
23
+ "devDependencies": {
24
+ "@biomejs/biome": "2.3.8",
25
+ "@types/node": "^24.10.0",
26
+ "@vitejs/plugin-vue": "^6.0.1",
27
+ "@vue/tsconfig": "^0.8.1",
28
+ "@webitel/styleguide": "^24.12.61",
29
+ "@webitel/ui-sdk": "~25.12",
30
+ "typescript": "~5.9.3",
31
+ "vite": "npm:rolldown-vite@7.2.2",
32
+ "vue-tsc": "^3.1.3"
33
+ },
34
+ "type": "module",
35
+ "main": "index.ts",
36
+ "types": "index.d.ts",
37
+ "files": [
38
+ "src/*",
39
+ "types/*",
40
+ "Readme.md",
41
+ "CHANGELOG.md"
42
+ ],
43
+ "exports": {
44
+ "./ui": {
45
+ "types": "./types/ui/index.d.ts",
46
+ "import": "./src/ui/index.ts"
47
+ }
48
+ },
49
+ "author": "webitel",
50
+ "license": "ISC",
51
+ "keywords": [
52
+ "webitel",
53
+ "webitel-ui"
54
+ ],
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "github.com/webitel/webitel-ui-sdk"
58
+ },
59
+ "engines": {
60
+ "npm": ">=10",
61
+ "node": ">=v22"
62
+ }
63
63
  }
@@ -1,9 +1,9 @@
1
1
  export default {
2
- '@webitel/ui-chats': {
3
- ui: {
4
- messaging: {
5
- chatsFileBlocked: 'TODO',
6
- },
7
- },
8
- },
9
- };
2
+ "@webitel/ui-chats": {
3
+ ui: {
4
+ messaging: {
5
+ chatsFileBlocked: "TODO",
6
+ },
7
+ },
8
+ },
9
+ };
@@ -35,63 +35,72 @@
35
35
  </template>
36
36
 
37
37
  <script setup lang="ts">
38
- import { provide, ref, computed } from 'vue';
39
- import { ComponentSize } from '@webitel/ui-sdk/enums';
40
- import ChatMessagesContainer from './messaging/components/chat-messages-container.vue';
38
+ import { ComponentSize } from "@webitel/ui-sdk/enums";
39
+ import { computed, provide, ref } from "vue";
40
+ import {
41
+ ChatAction,
42
+ type SharedActionSlots,
43
+ } from "./chat-footer/modules/user-input/types/ChatAction.types";
44
+ import type { ChatMessageType } from "./messaging/types/ChatMessage.types";
45
+ import { createUiChatsEmitter } from "./utils/emitter";
46
+ import type { ResultCallbacks } from "./utils/ResultCallbacks.types";
41
47
 
42
- import { ResultCallbacks } from './utils/ResultCallbacks.types';
43
- import ChatFooterWrapper from './chat-footer/components/chat-footer-wrapper.vue';
44
- import ChatTextField from './chat-footer/modules/user-input/components/chat-text-field.vue';
45
- import ChatInputActionsBar from './chat-footer/modules/user-input/components/chat-input-actions-bar.vue';
46
- import { createUiChatsEmitter } from './utils/emitter';
47
- import { ChatMessageType } from './messaging/types/ChatMessage.types';
48
- import { ChatAction, SharedActionSlots } from './chat-footer/modules/user-input/types/ChatAction.types';
49
-
50
- const props = withDefaults(defineProps<{
51
- messages: ChatMessageType[];
52
- chatActions?: ChatAction[];
53
- size?: ComponentSize;
54
- }>(), {
55
- size: ComponentSize.MD,
56
- chatActions: () => [ChatAction.SendMessage],
57
- });
48
+ const props = withDefaults(
49
+ defineProps<{
50
+ messages: ChatMessageType[];
51
+ chatActions?: ChatAction[];
52
+ size?: ComponentSize;
53
+ }>(),
54
+ {
55
+ size: ComponentSize.MD,
56
+ chatActions: () => [
57
+ ChatAction.SendMessage,
58
+ ],
59
+ },
60
+ );
58
61
 
59
62
  const emit = defineEmits<{
60
- (e: `action:${typeof ChatAction.SendMessage}`, text: string, options: ResultCallbacks): void;
61
- (e: `action:${typeof ChatAction.AttachFiles}`, files: File[], options: ResultCallbacks): void;
63
+ (
64
+ e: `action:${typeof ChatAction.SendMessage}`,
65
+ text: string,
66
+ options: ResultCallbacks,
67
+ ): void;
68
+ (
69
+ e: `action:${typeof ChatAction.AttachFiles}`,
70
+ files: File[],
71
+ options: ResultCallbacks,
72
+ ): void;
62
73
  }>();
63
74
 
64
- const slots = defineSlots<{
65
- main: () => any;
66
- footer: () => any;
67
- } & SharedActionSlots>();
75
+ const slots = defineSlots<
76
+ {
77
+ main: () => any;
78
+ footer: () => any;
79
+ } & SharedActionSlots
80
+ >();
68
81
 
69
82
  const uiChatsEmitter = createUiChatsEmitter();
70
83
 
71
- provide('size', props.size);
72
- provide('uiChatsEmitter', uiChatsEmitter);
84
+ provide("size", props.size);
85
+ provide("uiChatsEmitter", uiChatsEmitter);
73
86
 
74
- const draft = ref<string>('');
87
+ const draft = ref<string>("");
75
88
 
76
- const slottedChatActions = computed(() => {
77
- return Object.keys(slots)
78
- .filter((key) => key.startsWith('action:'))
79
- .map((key) => key.replace('action:', ''));
89
+ const _slottedChatActions = computed(() => {
90
+ return Object.keys(slots)
91
+ .filter((key) => key.startsWith("action:"))
92
+ .map((key) => key.replace("action:", ""));
80
93
  });
81
94
 
82
- function sendMessage() {
83
- emit(`action:${ChatAction.SendMessage}`,
84
- draft.value,
85
- {
86
- onSuccess: () => draft.value = '',
87
- });
95
+ function _sendMessage() {
96
+ emit(`action:${ChatAction.SendMessage}`, draft.value, {
97
+ onSuccess: () => (draft.value = ""),
98
+ });
88
99
  }
89
100
 
90
- function sendFile(files: File[]) {
91
- emit(`action:${ChatAction.AttachFiles}`, files, {
92
- });
101
+ function _sendFile(files: File[]) {
102
+ emit(`action:${ChatAction.AttachFiles}`, files, {});
93
103
  }
94
-
95
104
  </script>
96
105
 
97
106
  <style scoped>
@@ -5,11 +5,9 @@
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
-
9
- const slots = defineSlots<{
10
- default: () => any;
8
+ const _slots = defineSlots<{
9
+ default: () => any;
11
10
  }>();
12
-
13
11
  </script>
14
12
 
15
13
  <style scoped>
@@ -17,23 +17,21 @@
17
17
  </template>
18
18
 
19
19
  <script setup lang="ts">
20
- import { inject, useTemplateRef } from 'vue';
21
- import { WtRoundedAction } from '@webitel/ui-sdk/components';
22
- import { ComponentSize } from '@webitel/ui-sdk/enums';
23
- import { ChatAction } from '../../types/ChatAction.types';
20
+ import type { ComponentSize } from "@webitel/ui-sdk/enums";
21
+ import { inject, useTemplateRef } from "vue";
22
+ import { ChatAction } from "../../types/ChatAction.types";
24
23
 
25
- const size = inject<ComponentSize>('size');
24
+ const _size = inject<ComponentSize>("size");
26
25
 
27
- const emit = defineEmits<{
28
- (e: typeof ChatAction.AttachFiles, files: File[]): void;
29
- }>();
26
+ const emit =
27
+ defineEmits<(e: typeof ChatAction.AttachFiles, files: File[]) => void>();
30
28
 
31
- const attachFilesInputRef = useTemplateRef('attachFilesInput');
29
+ const _attachFilesInputRef = useTemplateRef("attachFilesInput");
32
30
 
33
- const handleAttachmentInputChange = (event: Event) => {
34
- const files = (event.target as HTMLInputElement).files;
35
- if (!files) return;
36
- emit(ChatAction.AttachFiles, Array.from(files) as File[]);
31
+ const _handleAttachmentInputChange = (event: Event) => {
32
+ const files = (event.target as HTMLInputElement).files;
33
+ if (!files) return;
34
+ emit(ChatAction.AttachFiles, Array.from(files) as File[]);
37
35
  };
38
36
  </script>
39
37
 
@@ -6,14 +6,12 @@
6
6
  </template>
7
7
 
8
8
  <script setup lang="ts">
9
- import { inject } from 'vue';
10
- import { WtChatEmoji } from '@webitel/ui-sdk/components';
11
- import { ComponentSize } from '@webitel/ui-sdk/enums';
12
- import type { Emitter } from 'mitt';
9
+ import type { ComponentSize } from "@webitel/ui-sdk/enums";
10
+ import type { Emitter } from "mitt";
11
+ import { inject } from "vue";
13
12
 
14
- import type{ UiChatsEmitterEvents } from '../../../../../utils/emitter';
15
-
16
- const size = inject<ComponentSize>('size');
17
- const uiChatsEmitter = inject<Emitter<UiChatsEmitterEvents>>('uiChatsEmitter');
13
+ import type { UiChatsEmitterEvents } from "../../../../../utils/emitter";
18
14
 
15
+ const _size = inject<ComponentSize>("size");
16
+ const _uiChatsEmitter = inject<Emitter<UiChatsEmitterEvents>>("uiChatsEmitter");
19
17
  </script>
@@ -10,14 +10,11 @@
10
10
  </template>
11
11
 
12
12
  <script setup lang="ts">
13
- import { inject } from 'vue';
14
- import { ComponentSize } from '@webitel/ui-sdk/enums';
15
- import { ChatAction } from '../../types/ChatAction.types';
13
+ import type { ComponentSize } from "@webitel/ui-sdk/enums";
14
+ import { inject } from "vue";
15
+ import type { ChatAction } from "../../types/ChatAction.types";
16
16
 
17
- const size = inject<ComponentSize>('size');
18
-
19
- const emit = defineEmits<{
20
- (e: typeof ChatAction.SendMessage): void;
21
- }>();
17
+ const _size = inject<ComponentSize>("size");
22
18
 
19
+ const _emit = defineEmits<(e: typeof ChatAction.SendMessage) => void>();
23
20
  </script>
@@ -17,52 +17,49 @@
17
17
  </template>
18
18
 
19
19
  <script setup lang="ts">
20
- import { computed, inject } from 'vue';
21
- import { ComponentSize } from '@webitel/ui-sdk/enums';
20
+ import type { ComponentSize } from "@webitel/ui-sdk/enums";
21
+ import { computed, inject } from "vue";
22
+ import { ChatAction, type SharedActionSlots } from "../types/ChatAction.types";
23
+ import AttachFilesAction from "./actions/attach-files-action.vue";
24
+ import EmojiPickerAction from "./actions/emoji-picker-action.vue";
25
+ import SendMessageAction from "./actions/send-message-action.vue";
22
26
 
23
- import { ChatAction } from '../types/ChatAction.types';
24
- import SendMessageAction from './actions/send-message-action.vue';
25
- import AttachFilesAction from './actions/attach-files-action.vue';
26
- import EmojiPickerAction from './actions/emoji-picker-action.vue';
27
- import { SharedActionSlots } from '../types/ChatAction.types';
28
-
29
- const size = inject<ComponentSize>('size');
27
+ const _size = inject<ComponentSize>("size");
30
28
 
31
29
  const props = defineProps<{
32
- actions: ChatAction[];
30
+ actions: ChatAction[];
33
31
  }>();
34
32
 
35
- const emit = defineEmits<{
36
- (e: typeof ChatAction.SendMessage): void;
37
- (e: typeof ChatAction.AttachFiles, files: File[]): void;
33
+ const _emit = defineEmits<{
34
+ (e: typeof ChatAction.SendMessage): void;
35
+ (e: typeof ChatAction.AttachFiles, files: File[]): void;
38
36
  }>();
39
37
 
40
- const slots = defineSlots<SharedActionSlots>();
38
+ const _slots = defineSlots<SharedActionSlots>();
41
39
 
42
- const ShownActionComponentsList = computed(() => {
43
- /**
44
- * note! actions order is declared here and cannot be changed from outside
45
- */
46
- return [
47
- {
48
- action: ChatAction.AttachFiles,
49
- component: AttachFilesAction,
50
- },
51
- {
52
- action: ChatAction.EmojiPicker,
53
- component: EmojiPickerAction,
54
- },
55
- {
56
- action: ChatAction.QuickReplies,
57
- component: null, // has no component inside lib, should be provided by pkg-client application
58
- },
59
- {
60
- action: ChatAction.SendMessage,
61
- component: SendMessageAction,
62
- },
63
- ].filter(({ action }) => props.actions.includes(action));
40
+ const _ShownActionComponentsList = computed(() => {
41
+ /**
42
+ * note! actions order is declared here and cannot be changed from outside
43
+ */
44
+ return [
45
+ {
46
+ action: ChatAction.AttachFiles,
47
+ component: AttachFilesAction,
48
+ },
49
+ {
50
+ action: ChatAction.EmojiPicker,
51
+ component: EmojiPickerAction,
52
+ },
53
+ {
54
+ action: ChatAction.QuickReplies,
55
+ component: null, // has no component inside lib, should be provided by pkg-client application
56
+ },
57
+ {
58
+ action: ChatAction.SendMessage,
59
+ component: SendMessageAction,
60
+ },
61
+ ].filter(({ action }) => props.actions.includes(action));
64
62
  });
65
-
66
63
  </script>
67
64
 
68
65
  <style scoped>
@@ -8,32 +8,37 @@
8
8
  </template>
9
9
 
10
10
  <script setup lang="ts">
11
- import { computed, inject, useTemplateRef, MaybeRef } from 'vue';
12
- import { WtTextarea } from '@webitel/ui-sdk/components';
13
- import { ComponentSize } from '@webitel/ui-sdk/enums';
14
- import insertTextAtCursor from 'insert-text-at-cursor';
15
- import type { Emitter } from 'mitt';
11
+ import type { WtTextarea } from "@webitel/ui-sdk/components";
12
+ import type { ComponentSize } from "@webitel/ui-sdk/enums";
13
+ import insertTextAtCursor from "insert-text-at-cursor";
14
+ import type { Emitter } from "mitt";
15
+ import { computed, inject, type MaybeRef, useTemplateRef } from "vue";
16
16
 
17
- import type { UiChatsEmitterEvents } from '../../../../utils/emitter';
17
+ import type { UiChatsEmitterEvents } from "../../../../utils/emitter";
18
18
 
19
- const textModel = defineModel<MaybeRef<string>>('text', { required: true });
19
+ const _textModel = defineModel<MaybeRef<string>>("text", {
20
+ required: true,
21
+ });
20
22
 
21
- const size = inject<ComponentSize>('size');
22
- const uiChatsEmitter = inject<Emitter<UiChatsEmitterEvents>>('uiChatsEmitter');
23
+ const _size = inject<ComponentSize>("size");
24
+ const uiChatsEmitter = inject<Emitter<UiChatsEmitterEvents>>("uiChatsEmitter");
23
25
 
24
- uiChatsEmitter!.on('insertAtCursor', ({ text }) => insertAtCursor(text));
25
- uiChatsEmitter!.on('focusOnTextField', focus);
26
+ uiChatsEmitter?.on("insertAtCursor", ({ text }) => insertAtCursor(text));
27
+ uiChatsEmitter?.on("focusOnTextField", focus);
26
28
 
27
- const chatTextFieldInputRef = useTemplateRef<typeof WtTextarea>('chatTextFieldInput');
29
+ const chatTextFieldInputRef =
30
+ useTemplateRef<typeof WtTextarea>("chatTextFieldInput");
28
31
 
29
- const textareaEl = computed(() => chatTextFieldInputRef.value?.$el.querySelector('textarea'));
32
+ const textareaEl = computed(() =>
33
+ chatTextFieldInputRef.value?.$el.querySelector("textarea"),
34
+ );
30
35
 
31
36
  function focus() {
32
- textareaEl.value!.focus();
33
- };
37
+ textareaEl.value?.focus();
38
+ }
34
39
 
35
40
  function insertAtCursor(text: string) {
36
- focus();
37
- insertTextAtCursor(textareaEl.value!, text);
41
+ focus();
42
+ insertTextAtCursor(textareaEl.value!, text);
38
43
  }
39
44
  </script>
@@ -1,12 +1,12 @@
1
1
  export const ChatAction = {
2
- SendMessage: 'sendMessage',
3
- AttachFiles: 'attachFiles',
4
- EmojiPicker: 'emojiPicker',
5
- QuickReplies: 'quickReplies',
2
+ SendMessage: "sendMessage",
3
+ AttachFiles: "attachFiles",
4
+ EmojiPicker: "emojiPicker",
5
+ QuickReplies: "quickReplies",
6
6
  } as const;
7
7
 
8
8
  export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction];
9
9
 
10
10
  export type SharedActionSlots = {
11
- [key in `action:${ChatAction}`]?: () => any;
11
+ [key in `action:${ChatAction}`]?: () => any;
12
12
  };
package/src/ui/index.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { default as ChatContainerComponent } from './chat-container.vue';
2
- export { ChatAction } from './chat-footer/modules/user-input/types/ChatAction.types';
1
+ export { default as ChatContainerComponent } from "./chat-container.vue";
2
+ export { ChatAction } from "./chat-footer/modules/user-input/types/ChatAction.types";
3
+ export type { ChatMessageType } from "./messaging/types/ChatMessage.types";
@@ -9,11 +9,11 @@
9
9
  </template>
10
10
 
11
11
  <script setup lang="ts">
12
- import { ChatMessageType } from '../types/ChatMessage.types';
13
- // import ChatMessageComponent from '../modules/message/components/chat-message.vue';
12
+ import type { ChatMessageType } from "../types/ChatMessage.types";
14
13
 
14
+ // import ChatMessageComponent from '../modules/message/components/chat-message.vue';
15
15
 
16
- const props = defineProps<{
17
- messages: ChatMessageType[];
16
+ const _props = defineProps<{
17
+ messages: ChatMessageType[];
18
18
  }>();
19
19
  </script>
@@ -0,0 +1,102 @@
1
+ import { useScroll } from "@vueuse/core";
2
+ import { computed, type Ref, ref, watch } from "vue";
3
+
4
+ import type { ChatMessageType } from "../types/ChatMessage.types";
5
+
6
+ export const useChatScroll = (
7
+ element: Ref<HTMLElement | null> = null,
8
+ chatMessages: ChatMessageType[] = [],
9
+ ) => {
10
+ const defaultThreshold = 136;
11
+ const { arrivedState } = useScroll(element.value);
12
+
13
+ const newUnseenMessages = ref<number>(0);
14
+ const showScrollToBottomBtn = ref<boolean>(false);
15
+ /* @author ye.pohranichna
16
+ the distance where the scrollToBottomBtn must be shown/hide.
17
+ why defaultThreshold=136px? because: https://webitel.atlassian.net/browse/WTEL-7136 */
18
+ const threshold = ref<number>(defaultThreshold);
19
+ const messages = ref<ChatMessageType[]>(chatMessages);
20
+
21
+ const lastMessage = computed<ChatMessageType>(
22
+ () => messages.value[messages.value?.length - 1],
23
+ );
24
+
25
+ const isLastMessageIsMy = computed<boolean>(() =>
26
+ Boolean(lastMessage.value?.member?.self),
27
+ );
28
+
29
+ const scrollToBottom = (behavior: ScrollBehavior = "instant") => {
30
+ element.value?.scrollTo({
31
+ top: element.value?.scrollHeight,
32
+ behavior: behavior === "instant" ? "auto" : behavior,
33
+ });
34
+
35
+ newUnseenMessages.value = 0;
36
+ showScrollToBottomBtn.value = false;
37
+ };
38
+
39
+ const handleShowScrollToBottomBtn = (el: HTMLElement) => {
40
+ if (arrivedState.bottom && newUnseenMessages.value) {
41
+ /* @author ye.pohranichna
42
+ hide the btn and reset new messages count, when we scroll to the bottom without btn */
43
+ newUnseenMessages.value = 0;
44
+ showScrollToBottomBtn.value = false;
45
+ /* @author ye.pohranichna
46
+ quit the function because we are already at the bottom */
47
+ return;
48
+ }
49
+
50
+ const { scrollTop, scrollHeight, clientHeight } = el;
51
+ const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
52
+ showScrollToBottomBtn.value = distanceFromBottom > threshold.value;
53
+ };
54
+
55
+ const updateThreshold = (el: HTMLElement) => {
56
+ /* @author ye.pohranichna
57
+ need to update if clientHeight was changed
58
+ https://webitel.atlassian.net/browse/WTEL-7136 */
59
+ threshold.value = Math.max(defaultThreshold, el.clientHeight * 0.3);
60
+ };
61
+
62
+ const scrollToBottomAfterNewMessage = () => {
63
+ if (arrivedState.bottom || isLastMessageIsMy.value) {
64
+ scrollToBottom("smooth");
65
+ } else {
66
+ newUnseenMessages.value += 1;
67
+ }
68
+ };
69
+ const handleChatScroll = () => {
70
+ const wrapper = element.value;
71
+ if (!wrapper) return;
72
+
73
+ handleShowScrollToBottomBtn(wrapper);
74
+ };
75
+
76
+ const handleChatResize = () => {
77
+ const wrapper = element.value;
78
+ if (!wrapper) return;
79
+
80
+ updateThreshold(wrapper);
81
+ handleShowScrollToBottomBtn(wrapper);
82
+ };
83
+
84
+ watch(
85
+ () => messages.value?.length,
86
+ (newValue, oldValue) => {
87
+ const newMessageReceived = newValue - oldValue === 1; // when chat have just 1 new message @author ye.pohranichna
88
+ if (newMessageReceived) scrollToBottomAfterNewMessage();
89
+ },
90
+ {
91
+ flush: "post",
92
+ },
93
+ );
94
+
95
+ return {
96
+ showScrollToBottomBtn,
97
+ newUnseenMessages,
98
+ scrollToBottom,
99
+ handleChatScroll,
100
+ handleChatResize,
101
+ };
102
+ };