@progress/kendo-angular-conversational-ui 21.0.0-develop.8 → 21.0.0

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 (69) hide show
  1. package/chat/api/files-layout.d.ts +12 -0
  2. package/chat/api/index.d.ts +4 -1
  3. package/chat/api/message-settings.interface.d.ts +41 -0
  4. package/chat/api/message-width-mode.d.ts +1 -1
  5. package/chat/api/message.interface.d.ts +5 -1
  6. package/chat/api/suggestions-layout.d.ts +20 -0
  7. package/chat/api/timestamp-visibility.d.ts +10 -0
  8. package/chat/chat.component.d.ts +114 -35
  9. package/chat/chat.directives.d.ts +18 -0
  10. package/chat/chat.module.d.ts +15 -8
  11. package/chat/common/chat.service.d.ts +36 -3
  12. package/chat/common/models/model-fields.d.ts +0 -6
  13. package/chat/common/scroll-button.component.d.ts +32 -0
  14. package/chat/common/scroll.service.d.ts +39 -0
  15. package/chat/common/utils.d.ts +13 -1
  16. package/chat/l10n/messages.d.ts +9 -1
  17. package/chat/message-list.component.d.ts +22 -3
  18. package/chat/message.component.d.ts +40 -16
  19. package/chat/suggested-actions.component.d.ts +30 -5
  20. package/chat/templates/author-message-content-template.directive.d.ts +28 -0
  21. package/chat/templates/author-message-template.directive.d.ts +28 -0
  22. package/chat/templates/message-content-template.directive.d.ts +28 -0
  23. package/chat/templates/message-template.directive.d.ts +1 -1
  24. package/chat/templates/no-data-template.directive.d.ts +27 -0
  25. package/chat/templates/receiver-message-content-template.directive.d.ts +28 -0
  26. package/chat/templates/receiver-message-template.directive.d.ts +28 -0
  27. package/chat/templates/user-status-template.directive.d.ts +27 -0
  28. package/codemods/utils.js +477 -375
  29. package/codemods/v20/chat-user.js +7 -5
  30. package/codemods/v21/chat-messagetoolbarvisibility.js +53 -0
  31. package/codemods/v21/chat-pinnedbyfield.js +19 -0
  32. package/conversational-ui.module.d.ts +18 -11
  33. package/directives.d.ts +9 -2
  34. package/esm2022/chat/api/index.mjs +4 -1
  35. package/{chat/api/message-toolbar-visibility.d.ts → esm2022/chat/api/message-settings.interface.mjs} +1 -4
  36. package/esm2022/chat/api/suggestions-layout.mjs +5 -0
  37. package/esm2022/chat/api/timestamp-visibility.mjs +5 -0
  38. package/esm2022/chat/builtin-actions.mjs +2 -0
  39. package/esm2022/chat/chat-file.component.mjs +2 -2
  40. package/esm2022/chat/chat.component.mjs +298 -72
  41. package/esm2022/chat/chat.directives.mjs +18 -0
  42. package/esm2022/chat/chat.module.mjs +16 -9
  43. package/esm2022/chat/common/chat.service.mjs +86 -4
  44. package/esm2022/chat/common/models/default-model-fields.mjs +0 -1
  45. package/esm2022/chat/common/scroll-button.component.mjs +81 -0
  46. package/esm2022/chat/common/scroll.service.mjs +110 -0
  47. package/esm2022/chat/common/utils.mjs +22 -3
  48. package/esm2022/chat/l10n/messages.mjs +13 -1
  49. package/esm2022/chat/message-attachments.component.mjs +2 -2
  50. package/esm2022/chat/message-box.component.mjs +5 -2
  51. package/esm2022/chat/message-list.component.mjs +184 -40
  52. package/esm2022/chat/message.component.mjs +487 -326
  53. package/esm2022/chat/suggested-actions.component.mjs +298 -80
  54. package/esm2022/chat/templates/author-message-content-template.directive.mjs +39 -0
  55. package/esm2022/chat/templates/author-message-template.directive.mjs +39 -0
  56. package/esm2022/chat/templates/message-content-template.directive.mjs +39 -0
  57. package/esm2022/chat/templates/message-template.directive.mjs +1 -1
  58. package/esm2022/chat/templates/no-data-template.directive.mjs +38 -0
  59. package/esm2022/chat/templates/receiver-message-content-template.directive.mjs +39 -0
  60. package/esm2022/chat/templates/receiver-message-template.directive.mjs +39 -0
  61. package/esm2022/chat/templates/user-status-template.directive.mjs +38 -0
  62. package/esm2022/conversational-ui.module.mjs +19 -12
  63. package/esm2022/directives.mjs +15 -1
  64. package/esm2022/index.mjs +7 -0
  65. package/esm2022/package-metadata.mjs +2 -2
  66. package/fesm2022/progress-kendo-angular-conversational-ui.mjs +1825 -568
  67. package/index.d.ts +7 -0
  68. package/package.json +26 -14
  69. /package/esm2022/chat/api/{message-toolbar-visibility.mjs → files-layout.mjs} +0 -0
@@ -3,15 +3,15 @@
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
5
  import * as i0 from '@angular/core';
6
- import { InjectionToken, Directive, Inject, HostBinding, ViewChild, Input, Injectable, Optional, forwardRef, EventEmitter, Component, ContentChildren, ContentChild, Output, isDevMode, ViewChildren, HostListener, ElementRef, ViewContainerRef, inject, SkipSelf, NgModule } from '@angular/core';
6
+ import { InjectionToken, Directive, Inject, HostBinding, ViewChild, Input, Injectable, Optional, forwardRef, EventEmitter, Component, ContentChildren, ContentChild, Output, isDevMode, ElementRef, ViewChildren, HostListener, ViewContainerRef, inject, SkipSelf, NgModule } from '@angular/core';
7
7
  import { IconWrapperComponent, IconsService } from '@progress/kendo-angular-icons';
8
8
  import * as i1$2 from '@progress/kendo-angular-popup';
9
9
  import { PopupComponent, KENDO_POPUP, PopupService } from '@progress/kendo-angular-popup';
10
- import { isPresent, normalizeNumpadKeys, Keys, focusableSelector, guid, getter, closest as closest$1, ResizeSensorComponent, isChanged, processCssValue, isDocumentAvailable, ResizeBatchService } from '@progress/kendo-angular-common';
10
+ import { isPresent, normalizeNumpadKeys, Keys, focusableSelector, guid, getter, isDocumentAvailable, closest as closest$1, ResizeSensorComponent, isChanged, processCssValue, ResizeBatchService } from '@progress/kendo-angular-common';
11
11
  import { DialogContainerService, DialogService, WindowService, WindowContainerService } from '@progress/kendo-angular-dialog';
12
12
  import { NgFor, NgIf, NgTemplateOutlet, NgClass, NgSwitch, NgSwitchCase } from '@angular/common';
13
13
  import { Subject, Subscription, fromEvent } from 'rxjs';
14
- import { sparklesIcon, commentIcon, moreHorizontalIcon, stopSmIcon, thumbUpIcon, thumbDownOutlineIcon, thumbDownIcon, thumbUpOutlineIcon, copyIcon, arrowRotateCwIcon, chevronUpIcon, chevronDownIcon, paperPlaneIcon, undoIcon, downloadIcon, xIcon, moreVerticalIcon, paperclipIcon, fileIcon, chevronLeftIcon, chevronRightIcon, pinIcon, cancelOutlineIcon, menuIcon } from '@progress/kendo-svg-icons';
14
+ import { sparklesIcon, commentIcon, moreHorizontalIcon, stopSmIcon, thumbUpIcon, thumbDownOutlineIcon, thumbDownIcon, thumbUpOutlineIcon, copyIcon, arrowRotateCwIcon, chevronUpIcon, chevronDownIcon, paperPlaneIcon, undoIcon, downloadIcon, chevronLeftIcon, chevronRightIcon, xIcon, moreVerticalIcon, paperclipIcon, fileIcon, pinIcon, cancelOutlineIcon, menuIcon } from '@progress/kendo-svg-icons';
15
15
  import * as i1 from '@progress/kendo-angular-l10n';
16
16
  import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
17
17
  import { validatePackage } from '@progress/kendo-licensing';
@@ -25,10 +25,10 @@ import { TextAreaComponent, TextAreaSuffixComponent, TextAreaPrefixComponent, In
25
25
  import { fileSVGGroupIcon, fileGroupClass, getTotalFilesSizeMessage, FileSelectComponent } from '@progress/kendo-angular-upload';
26
26
  import { FormsModule } from '@angular/forms';
27
27
  import * as i1$1 from '@progress/kendo-angular-intl';
28
- import * as i4 from '@progress/kendo-angular-menu';
29
- import { ContextMenuComponent, KENDO_CONTEXTMENU } from '@progress/kendo-angular-menu';
30
28
  import { ToolBarComponent, ToolBarButtonComponent } from '@progress/kendo-angular-toolbar';
31
29
  import { AppBarComponent } from '@progress/kendo-angular-navigation';
30
+ import * as i4 from '@progress/kendo-angular-menu';
31
+ import { ContextMenuComponent, KENDO_CONTEXTMENU } from '@progress/kendo-angular-menu';
32
32
 
33
33
  /**
34
34
  * @hidden
@@ -216,8 +216,8 @@ const packageMetadata = {
216
216
  productName: 'Kendo UI for Angular',
217
217
  productCode: 'KENDOUIANGULAR',
218
218
  productCodes: ['KENDOUIANGULAR'],
219
- publishDate: 1761844485,
220
- version: '21.0.0-develop.8',
219
+ publishDate: 1762934532,
220
+ version: '21.0.0',
221
221
  licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/'
222
222
  };
223
223
 
@@ -1887,6 +1887,7 @@ const noop = () => { };
1887
1887
  const handlers = {
1888
1888
  'reply': (action, sender) => {
1889
1889
  sender.sendMessage.emit(new SendMessageEvent({
1890
+ id: guid(),
1890
1891
  author: { id: sender.authorId },
1891
1892
  text: action.value,
1892
1893
  timestamp: new Date()
@@ -1904,40 +1905,6 @@ const handlers = {
1904
1905
  */
1905
1906
  const makeHandler = (action) => handlers[action.type] || noop;
1906
1907
 
1907
- /**
1908
- * Defines a template for displaying Chat messages.
1909
- *
1910
- * To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageTemplate` directive inside the `<kendo-chat>` component.
1911
- * The template context is set to the `message` instance.
1912
- * For more information, refer to the article on [message templates](slug:message_templates_chat).
1913
- *
1914
- * @example
1915
- * ```html
1916
- * <kendo-chat>
1917
- * <ng-template kendoChatMessageTemplate let-message>
1918
- * <div>Message: {{ message.text }}</div>
1919
- * </ng-template>
1920
- * </kendo-chat>
1921
- * ```
1922
- */
1923
- class MessageTemplateDirective {
1924
- templateRef;
1925
- constructor(templateRef) {
1926
- this.templateRef = templateRef;
1927
- }
1928
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
1929
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageTemplateDirective, isStandalone: true, selector: "[kendoChatMessageTemplate]", ngImport: i0 });
1930
- }
1931
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageTemplateDirective, decorators: [{
1932
- type: Directive,
1933
- args: [{
1934
- selector: '[kendoChatMessageTemplate]',
1935
- standalone: true
1936
- }]
1937
- }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
1938
- type: Optional
1939
- }] }] });
1940
-
1941
1908
  /**
1942
1909
  * Creates a message box area that overrides the default message box of the Chat component.
1943
1910
  *
@@ -1986,7 +1953,6 @@ const defaultModelFields = {
1986
1953
  attachmentLayoutField: 'attachmentLayout',
1987
1954
  suggestedActionsField: 'suggestedActions',
1988
1955
  isPinnedField: 'isPinned',
1989
- pinnedByField: 'pinnedBy',
1990
1956
  replyToIdField: 'replyToId',
1991
1957
  isDeletedField: 'isDeleted',
1992
1958
  typingField: 'typing'
@@ -2053,6 +2019,10 @@ const FILESELECT_DEFAULT_SETTINGS = {
2053
2019
  multiple: true,
2054
2020
  disabled: false
2055
2021
  };
2022
+ /**
2023
+ * @hidden
2024
+ */
2025
+ const SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS = 'scroll';
2056
2026
  /**
2057
2027
  * @hidden
2058
2028
  */
@@ -2105,14 +2075,16 @@ const parseMessage = (message, fields) => {
2105
2075
  id: authorId,
2106
2076
  ...(authorName && { name: authorName }),
2107
2077
  ...(authorImageUrl && { avatarUrl: authorImageUrl }),
2108
- ...(authorImageAltText && { avatarAltText: authorImageAltText })
2078
+ ...(authorImageAltText && { avatarAltText: authorImageAltText }),
2109
2079
  };
2110
2080
  }
2081
+ const timestampValue = getter(modelFields.timestampField)(message);
2082
+ const timestamp = timestampValue instanceof Date ? timestampValue : new Date(timestampValue);
2111
2083
  return {
2112
2084
  id: getter(modelFields.idField)(message),
2113
2085
  text: getter(modelFields.textField)(message),
2114
2086
  author: author,
2115
- timestamp: new Date(getter(modelFields.timestampField)(message)),
2087
+ timestamp: timestamp,
2116
2088
  status: getter(modelFields.statusField)(message),
2117
2089
  files: getter(modelFields.filesField)(message),
2118
2090
  attachments: getter(modelFields.attachmentsField)(message),
@@ -2121,9 +2093,22 @@ const parseMessage = (message, fields) => {
2121
2093
  isPinned: getter(modelFields.isPinnedField)(message),
2122
2094
  replyToId: getter(modelFields.replyToIdField)(message),
2123
2095
  isDeleted: getter(modelFields.isDeletedField)(message),
2124
- typing: getter(modelFields.typingField)(message)
2096
+ typing: getter(modelFields.typingField)(message),
2097
+ dataItem: message
2125
2098
  };
2126
2099
  };
2100
+ /**
2101
+ * @hidden
2102
+ */
2103
+ const transformActions = (actions) => {
2104
+ return actions.map(action => ({
2105
+ text: action.label,
2106
+ icon: action.icon,
2107
+ svgIcon: action.svgIcon,
2108
+ disabled: action.disabled,
2109
+ originalAction: action
2110
+ }));
2111
+ };
2127
2112
 
2128
2113
  /**
2129
2114
  * @hidden
@@ -2131,26 +2116,45 @@ const parseMessage = (message, fields) => {
2131
2116
  class ChatService {
2132
2117
  authorId;
2133
2118
  messageWidthMode;
2134
- allowMessageCollapse;
2135
2119
  messageToolbarActions = [];
2136
2120
  messageContextMenuActions = [];
2121
+ calculatedContextMenuActions = [];
2137
2122
  fileActions = [];
2138
- messageToolbarVisibility = 'hidden';
2139
2123
  toggleMessageState = false;
2140
2124
  reply;
2141
2125
  messages = [];
2142
2126
  chatElement;
2143
2127
  messageElementsMap = new Map();
2128
+ messagesContextMenu;
2129
+ activeMessage;
2130
+ activeMessageElement;
2131
+ selectOnMenuClose = false;
2132
+ active = false;
2133
+ messageFilesLayout = 'vertical';
2134
+ timestampVisibility = 'focus';
2135
+ showUsername = true;
2136
+ showAvatar = true;
2144
2137
  _enableSpeechToText = STB_DEFAULT_SETTINGS;
2145
2138
  _enableFileSelect = FILESELECT_DEFAULT_SETTINGS;
2146
2139
  _sendButtonSettings = SEND_BTN_DEFAULT_SETTINGS;
2140
+ _suggestionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2141
+ _quickActionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2142
+ _authorMessageSettings;
2143
+ _receiverMessageSettings;
2144
+ _allowMessageCollapse;
2147
2145
  subjects = {
2148
2146
  toolbarAction: new Subject(),
2149
2147
  contextMenuAction: new Subject(),
2150
2148
  fileAction: new Subject(),
2151
2149
  fileDownload: new Subject(),
2152
2150
  replyReferenceClick: new Subject(),
2153
- inputValueChange: new Subject()
2151
+ inputValueChange: new Subject(),
2152
+ contextMenuVisibilityChange: new Subject(),
2153
+ suggestionsLayoutChange: new Subject(),
2154
+ quickActionsLayoutChange: new Subject(),
2155
+ authorMessageSettingsChange: new Subject(),
2156
+ receiverMessageSettingsChange: new Subject(),
2157
+ allowMessageCollapseChange: new Subject(),
2154
2158
  };
2155
2159
  toolbarAction$ = this.subjects.toolbarAction.asObservable();
2156
2160
  contextMenuAction$ = this.subjects.contextMenuAction.asObservable();
@@ -2158,6 +2162,32 @@ class ChatService {
2158
2162
  fileDownload$ = this.subjects.fileDownload.asObservable();
2159
2163
  replyReferenceClick$ = this.subjects.replyReferenceClick.asObservable();
2160
2164
  inputValueChange$ = this.subjects.inputValueChange.asObservable();
2165
+ contextMenuVisibilityChange$ = this.subjects.contextMenuVisibilityChange.asObservable();
2166
+ suggestionsLayoutChange$ = this.subjects.suggestionsLayoutChange.asObservable();
2167
+ quickActionsLayoutChange$ = this.subjects.quickActionsLayoutChange.asObservable();
2168
+ authorMessageSettingsChange$ = this.subjects.authorMessageSettingsChange.asObservable();
2169
+ receiverMessageSettingsChange$ = this.subjects.receiverMessageSettingsChange.asObservable();
2170
+ allowMessageCollapseChange$ = this.subjects.allowMessageCollapseChange.asObservable();
2171
+ set authorMessageSettings(settings) {
2172
+ const previousSettings = this._authorMessageSettings;
2173
+ if (JSON.stringify(previousSettings) !== JSON.stringify(settings)) {
2174
+ this.updateComponentSettings('_authorMessageSettings', settings, null);
2175
+ this.emit('authorMessageSettingsChange', this._authorMessageSettings);
2176
+ }
2177
+ }
2178
+ get authorMessageSettings() {
2179
+ return this._authorMessageSettings;
2180
+ }
2181
+ set receiverMessageSettings(settings) {
2182
+ const previousSettings = this._receiverMessageSettings;
2183
+ if (JSON.stringify(previousSettings) !== JSON.stringify(settings)) {
2184
+ this.updateComponentSettings('_receiverMessageSettings', settings, null);
2185
+ this.emit('receiverMessageSettingsChange', this._receiverMessageSettings);
2186
+ }
2187
+ }
2188
+ get receiverMessageSettings() {
2189
+ return this._receiverMessageSettings;
2190
+ }
2161
2191
  set enableSpeechToText(settings) {
2162
2192
  this.updateComponentSettings('_enableSpeechToText', settings, STB_DEFAULT_SETTINGS);
2163
2193
  }
@@ -2176,6 +2206,38 @@ class ChatService {
2176
2206
  get sendButtonSettings() {
2177
2207
  return this._sendButtonSettings;
2178
2208
  }
2209
+ set suggestionsLayout(layoutMode) {
2210
+ this._suggestionsLayout = layoutMode;
2211
+ this.emit('suggestionsLayoutChange', this._suggestionsLayout);
2212
+ }
2213
+ get suggestionsLayout() {
2214
+ return this._suggestionsLayout;
2215
+ }
2216
+ set quickActionsLayout(layoutMode) {
2217
+ this._quickActionsLayout = layoutMode;
2218
+ this.emit('quickActionsLayoutChange', this._quickActionsLayout);
2219
+ }
2220
+ get quickActionsLayout() {
2221
+ return this._quickActionsLayout;
2222
+ }
2223
+ set allowMessageCollapse(value) {
2224
+ const previousValue = this._allowMessageCollapse;
2225
+ if (previousValue !== value) {
2226
+ this._allowMessageCollapse = value;
2227
+ this.emit('allowMessageCollapseChange', this._allowMessageCollapse);
2228
+ }
2229
+ }
2230
+ get allowMessageCollapse() {
2231
+ return this._allowMessageCollapse;
2232
+ }
2233
+ calculateContextMenuActions(isOwn) {
2234
+ const settings = isOwn ? this.authorMessageSettings : this.receiverMessageSettings;
2235
+ if (settings?.messageContextMenuActions) {
2236
+ this.calculatedContextMenuActions = settings.messageContextMenuActions;
2237
+ return;
2238
+ }
2239
+ this.calculatedContextMenuActions = this.messageContextMenuActions || [];
2240
+ }
2179
2241
  emit(subjectKey, value) {
2180
2242
  (this.subjects[subjectKey]).next(value);
2181
2243
  }
@@ -2197,6 +2259,11 @@ class ChatService {
2197
2259
  elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
2198
2260
  }
2199
2261
  }
2262
+ focusActiveMessageElement() {
2263
+ if (this.activeMessageElement) {
2264
+ this.activeMessageElement.element?.nativeElement?.focus();
2265
+ }
2266
+ }
2200
2267
  updateComponentSettings(property, settings, defaultSettings) {
2201
2268
  if (settings === true) {
2202
2269
  this[property] = defaultSettings;
@@ -2253,24 +2320,238 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2253
2320
  }]
2254
2321
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
2255
2322
 
2323
+ const DIRECTION_CLASSES = {
2324
+ left: 'chevron-left',
2325
+ right: 'chevron-right',
2326
+ };
2327
+ /**
2328
+ * @hidden
2329
+ */
2330
+ class ChatScrollableButtonComponent {
2331
+ host;
2332
+ renderer;
2333
+ ngZone;
2334
+ localization;
2335
+ role = 'button';
2336
+ prev = false;
2337
+ onClick = new EventEmitter();
2338
+ get scrollButtonIconClass() {
2339
+ const defaultPrevIcon = !this.localization.rtl ? DIRECTION_CLASSES.left : DIRECTION_CLASSES.right;
2340
+ const defaultNextIcon = !this.localization.rtl ? DIRECTION_CLASSES.right : DIRECTION_CLASSES.left;
2341
+ return this.prev ? defaultPrevIcon : defaultNextIcon;
2342
+ }
2343
+ get scrollButtonSVGIcon() {
2344
+ const defaultPrevSVGIcon = !this.localization.rtl ? this.chevronLeftIcon : this.chevronRightIcon;
2345
+ const defaultNextSVGIcon = !this.localization.rtl ? this.chevronRightIcon : this.chevronLeftIcon;
2346
+ return this.prev ? defaultPrevSVGIcon : defaultNextSVGIcon;
2347
+ }
2348
+ chevronLeftIcon = chevronLeftIcon;
2349
+ chevronRightIcon = chevronRightIcon;
2350
+ subs = new Subscription();
2351
+ constructor(host, renderer, ngZone, localization) {
2352
+ this.host = host;
2353
+ this.renderer = renderer;
2354
+ this.ngZone = ngZone;
2355
+ this.localization = localization;
2356
+ }
2357
+ ngAfterViewInit() {
2358
+ this.ngZone.runOutsideAngular(() => {
2359
+ this.subs.add(this.renderer.listen(this.host.nativeElement, 'click', () => this.clickHandler()));
2360
+ });
2361
+ }
2362
+ ngOnDestroy() {
2363
+ this.subs.unsubscribe();
2364
+ }
2365
+ clickHandler() {
2366
+ const buttonType = this.prev ? 'prev' : 'next';
2367
+ this.onClick.emit(buttonType);
2368
+ }
2369
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatScrollableButtonComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
2370
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChatScrollableButtonComponent, isStandalone: true, selector: "[kendoChatScrollableButton]", inputs: { prev: "prev" }, outputs: { onClick: "onClick" }, host: { properties: { "attr.role": "this.role" } }, ngImport: i0, template: `
2371
+ <kendo-icon-wrapper [name]="scrollButtonIconClass" [svgIcon]="scrollButtonSVGIcon" innerCssClass="k-button-icon"> </kendo-icon-wrapper>
2372
+ `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] });
2373
+ }
2374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatScrollableButtonComponent, decorators: [{
2375
+ type: Component,
2376
+ args: [{
2377
+ template: `
2378
+ <kendo-icon-wrapper [name]="scrollButtonIconClass" [svgIcon]="scrollButtonSVGIcon" innerCssClass="k-button-icon"> </kendo-icon-wrapper>
2379
+ `,
2380
+ // eslint-disable-next-line @angular-eslint/component-selector
2381
+ selector: '[kendoChatScrollableButton]',
2382
+ standalone: true,
2383
+ imports: [IconWrapperComponent],
2384
+ }]
2385
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }], propDecorators: { role: [{
2386
+ type: HostBinding,
2387
+ args: ['attr.role']
2388
+ }], prev: [{
2389
+ type: Input
2390
+ }], onClick: [{
2391
+ type: Output
2392
+ }] } });
2393
+
2394
+ const DEFAULT_SCROLL_BEHAVIOR = 'smooth';
2395
+ const DEFAULT_SCROLL_SPEED = 100;
2396
+ /**
2397
+ * @hidden
2398
+ */
2399
+ class SuggestionsScrollService {
2400
+ ngZone;
2401
+ localization;
2402
+ owner;
2403
+ position = 0;
2404
+ scrollButtonActiveStateChange = new Subject();
2405
+ get scrollElement() {
2406
+ return this.owner.suggestionsContainer?.nativeElement;
2407
+ }
2408
+ get scrollContainerOverflowSize() {
2409
+ if (!isDocumentAvailable()) {
2410
+ return 0;
2411
+ }
2412
+ if (!this.scrollElement) {
2413
+ return 0;
2414
+ }
2415
+ const overflowSize = Math.floor(this.scrollElement.scrollWidth - this.scrollElement.offsetWidth);
2416
+ return overflowSize < 0 ? 0 : overflowSize;
2417
+ }
2418
+ get suggestionsOverflow() {
2419
+ return this.scrollContainerOverflowSize > 0;
2420
+ }
2421
+ constructor(ngZone, localization) {
2422
+ this.ngZone = ngZone;
2423
+ this.localization = localization;
2424
+ }
2425
+ toggleScrollButtonsState() {
2426
+ const suggestedActions = this.owner;
2427
+ if (!suggestedActions?.hasScrollButtons) {
2428
+ return;
2429
+ }
2430
+ const currentPrevButtonActive = !this.isDisabled('prev');
2431
+ const currentNextButtonActive = !this.isDisabled('next');
2432
+ const defaultOffset = 1;
2433
+ const rtlDelta = this.localization.rtl ? -1 : 1;
2434
+ const calculatedPrevButtonActive = (this.position * rtlDelta) > 0 && this.scrollContainerOverflowSize > 0;
2435
+ const calculatedNextButtonActive = (this.position * rtlDelta) < this.scrollContainerOverflowSize - defaultOffset && this.scrollContainerOverflowSize > 0;
2436
+ if (calculatedPrevButtonActive !== currentPrevButtonActive) {
2437
+ this.ngZone.run(() => this.toggleButtonActiveState('prev', calculatedPrevButtonActive));
2438
+ }
2439
+ if (calculatedNextButtonActive !== currentNextButtonActive) {
2440
+ this.ngZone.run(() => this.toggleButtonActiveState('next', calculatedNextButtonActive));
2441
+ }
2442
+ }
2443
+ onScroll(e) {
2444
+ this.position = e.target.scrollLeft;
2445
+ this.toggleScrollButtonsState();
2446
+ }
2447
+ scrollSuggestions(direction) {
2448
+ this.calculateListPosition(direction, DEFAULT_SCROLL_SPEED);
2449
+ if (this.scrollElement) {
2450
+ this.scrollElement.scrollTo({ left: this.position, behavior: DEFAULT_SCROLL_BEHAVIOR });
2451
+ }
2452
+ this.toggleScrollButtonsState();
2453
+ }
2454
+ updateScrollPosition(element) {
2455
+ this.position = element.scrollLeft;
2456
+ }
2457
+ calculateListPosition(direction, scrollSpeed) {
2458
+ if (direction === 'prev') {
2459
+ if (!this.localization.rtl) {
2460
+ this.position = this.position - scrollSpeed <= 0 ? 0 : this.position - scrollSpeed;
2461
+ }
2462
+ else {
2463
+ this.position = this.position + scrollSpeed >= 0 ? 0 : this.position + scrollSpeed;
2464
+ }
2465
+ }
2466
+ else if (direction === 'next' && this.position < this.scrollContainerOverflowSize) {
2467
+ if (this.position + scrollSpeed > this.scrollContainerOverflowSize) {
2468
+ if (this.localization.rtl) {
2469
+ this.position = -this.scrollContainerOverflowSize;
2470
+ }
2471
+ else {
2472
+ this.position = this.scrollContainerOverflowSize;
2473
+ }
2474
+ return;
2475
+ }
2476
+ if (this.localization.rtl) {
2477
+ this.position -= scrollSpeed;
2478
+ }
2479
+ else {
2480
+ this.position += scrollSpeed;
2481
+ }
2482
+ }
2483
+ }
2484
+ toggleButtonActiveState(buttonType, active) {
2485
+ this.scrollButtonActiveStateChange.next({ buttonType, active });
2486
+ }
2487
+ isDisabled = (buttonType) => this.owner[`${buttonType}ScrollButton`]?.nativeElement.classList.contains('k-disabled');
2488
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestionsScrollService, deps: [{ token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Injectable });
2489
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestionsScrollService });
2490
+ }
2491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestionsScrollService, decorators: [{
2492
+ type: Injectable
2493
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i1.LocalizationService }] });
2494
+
2256
2495
  /* eslint-disable @typescript-eslint/no-explicit-any */
2257
2496
  /* eslint-disable @typescript-eslint/no-empty-function */
2258
2497
  /**
2259
2498
  * @hidden
2260
2499
  */
2261
2500
  class SuggestedActionsComponent extends ChatItem {
2501
+ chatService;
2502
+ localization;
2503
+ scrollService;
2504
+ ngZone;
2505
+ renderer;
2506
+ get defaultClass() {
2507
+ if (this.type === 'suggestion') {
2508
+ return this.chatService.suggestionsLayout === 'wrap' || this.chatService.suggestionsLayout === 'scroll';
2509
+ }
2510
+ else if (this.type === 'action') {
2511
+ return this.chatService.quickActionsLayout === 'wrap' || this.chatService.quickActionsLayout === 'scroll';
2512
+ }
2513
+ }
2514
+ get scrollableClass() {
2515
+ if (this.type === 'suggestion') {
2516
+ return this.chatService.suggestionsLayout === 'scroll';
2517
+ }
2518
+ else if (this.type === 'action') {
2519
+ return this.chatService.quickActionsLayout === 'scroll';
2520
+ }
2521
+ }
2522
+ get scrollButtonsClass() {
2523
+ if (this.type === 'suggestion') {
2524
+ return this.chatService.suggestionsLayout === 'scrollbuttons';
2525
+ }
2526
+ else if (this.type === 'action') {
2527
+ return this.chatService.quickActionsLayout === 'scrollbuttons';
2528
+ }
2529
+ }
2530
+ get role() {
2531
+ if (!this.hasScrollButtons) {
2532
+ return 'group';
2533
+ }
2534
+ return null;
2535
+ }
2262
2536
  actions;
2263
2537
  suggestions;
2264
2538
  tabbable;
2539
+ type;
2265
2540
  suggestionTemplate;
2266
2541
  dispatchAction = new EventEmitter();
2267
2542
  dispatchSuggestion = new EventEmitter();
2268
- defaultClass = true;
2269
- role = 'group';
2270
2543
  items;
2544
+ suggestionsContainer;
2545
+ prevScrollButton;
2546
+ nextScrollButton;
2271
2547
  selectedIndex = 0;
2272
2548
  activeIndex = -1;
2273
2549
  active = false;
2550
+ get hasScrollButtons() {
2551
+ return this.type === 'suggestion' ? this.chatService.suggestionsLayout === 'scrollbuttons' : this.chatService.quickActionsLayout === 'scrollbuttons';
2552
+ }
2553
+ subscriptions = new Subscription();
2554
+ resizeObserver = null;
2274
2555
  actionKeyHandlers = {
2275
2556
  [Keys.Tab]: (e) => this.changeSelectedIndex(e),
2276
2557
  [Keys.Enter]: (_, action) => this.actionClick(action),
@@ -2281,6 +2562,58 @@ class SuggestedActionsComponent extends ChatItem {
2281
2562
  [Keys.Enter]: (_, suggestion) => this.suggestionClick(suggestion),
2282
2563
  [Keys.Space]: (_, suggestion) => this.suggestionClick(suggestion),
2283
2564
  };
2565
+ constructor(chatService, localization, scrollService, ngZone, renderer) {
2566
+ super();
2567
+ this.chatService = chatService;
2568
+ this.localization = localization;
2569
+ this.scrollService = scrollService;
2570
+ this.ngZone = ngZone;
2571
+ this.renderer = renderer;
2572
+ this.scrollService.owner = this;
2573
+ }
2574
+ ngAfterViewInit() {
2575
+ const layoutChangeObservable = this.type === 'suggestion'
2576
+ ? this.chatService.suggestionsLayoutChange$
2577
+ : this.chatService.quickActionsLayoutChange$;
2578
+ this.subscriptions.add(layoutChangeObservable.subscribe((layoutMode) => {
2579
+ if (layoutMode === 'scrollbuttons') {
2580
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
2581
+ if (this.suggestionsContainer) {
2582
+ this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement);
2583
+ }
2584
+ this.scrollService.toggleScrollButtonsState();
2585
+ });
2586
+ }
2587
+ }));
2588
+ this.subscriptions.add(this.scrollService.scrollButtonActiveStateChange.subscribe((change) => {
2589
+ this.toggleScrollButtonState(change.buttonType, change.active);
2590
+ }));
2591
+ if (this.hasScrollButtons && this.suggestionsContainer) {
2592
+ this.ngZone.runOutsideAngular(() => {
2593
+ this.resizeObserver = new ResizeObserver(() => {
2594
+ this.ngZone.run(() => {
2595
+ this.scrollService.toggleScrollButtonsState();
2596
+ });
2597
+ });
2598
+ this.resizeObserver.observe(this.suggestionsContainer.nativeElement);
2599
+ });
2600
+ }
2601
+ if (this.hasScrollButtons) {
2602
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
2603
+ this.scrollService.toggleScrollButtonsState();
2604
+ });
2605
+ if (this.suggestionsContainer) {
2606
+ this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement);
2607
+ }
2608
+ }
2609
+ }
2610
+ ngOnDestroy() {
2611
+ this.subscriptions.unsubscribe();
2612
+ if (this.resizeObserver) {
2613
+ this.resizeObserver.disconnect();
2614
+ this.resizeObserver = null;
2615
+ }
2616
+ }
2284
2617
  isSelected(index) {
2285
2618
  return this.selected && this.selectedIndex === index;
2286
2619
  }
@@ -2314,67 +2647,130 @@ class SuggestedActionsComponent extends ChatItem {
2314
2647
  handler(e, suggestion);
2315
2648
  }
2316
2649
  }
2650
+ getScrollButtonTitle(direction) {
2651
+ let currentButton;
2652
+ if (this.localization.rtl) {
2653
+ currentButton = direction === 'prev' ? 'nextSuggestionsButtonTitle' : 'previousSuggestionsButtonTitle';
2654
+ }
2655
+ else {
2656
+ currentButton = direction === 'prev' ? 'previousSuggestionsButtonTitle' : 'nextSuggestionsButtonTitle';
2657
+ }
2658
+ return this.localization.get(currentButton);
2659
+ }
2660
+ onScroll(event) {
2661
+ this.scrollService.onScroll(event);
2662
+ }
2663
+ scrollSuggestions(direction) {
2664
+ this.scrollService.scrollSuggestions(direction);
2665
+ }
2317
2666
  focus() { }
2667
+ toggleScrollButtonState(buttonType, active) {
2668
+ const button = this[`${buttonType}ScrollButton`];
2669
+ if (button?.nativeElement) {
2670
+ if (active) {
2671
+ this.renderer.removeClass(button.nativeElement, 'k-disabled');
2672
+ }
2673
+ else {
2674
+ this.renderer.addClass(button.nativeElement, 'k-disabled');
2675
+ }
2676
+ }
2677
+ }
2318
2678
  changeSelectedIndex(e) {
2319
2679
  const offset = e.shiftKey ? -1 : 1;
2320
2680
  const prevIndex = this.selectedIndex;
2321
2681
  this.selectedIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1));
2322
2682
  }
2323
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2324
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SuggestedActionsComponent, isStandalone: true, selector: "kendo-chat-suggested-actions", inputs: { actions: "actions", suggestions: "suggestions", tabbable: "tabbable", suggestionTemplate: "suggestionTemplate" }, outputs: { dispatchAction: "dispatchAction", dispatchSuggestion: "dispatchSuggestion" }, host: { properties: { "class.k-suggestion-group": "this.defaultClass", "attr.role": "this.role" } }, providers: [{
2683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, deps: [{ token: ChatService }, { token: i1.LocalizationService }, { token: SuggestionsScrollService }, { token: i0.NgZone }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
2684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SuggestedActionsComponent, isStandalone: true, selector: "kendo-chat-suggested-actions", inputs: { actions: "actions", suggestions: "suggestions", tabbable: "tabbable", type: "type", suggestionTemplate: "suggestionTemplate" }, outputs: { dispatchAction: "dispatchAction", dispatchSuggestion: "dispatchSuggestion" }, host: { properties: { "class.k-suggestion-group": "this.defaultClass", "class.k-suggestion-group-scrollable": "this.scrollableClass", "class.k-suggestion-scrollwrap": "this.scrollButtonsClass", "attr.role": "this.role" } }, providers: [{
2325
2685
  provide: ChatItem,
2326
2686
  useExisting: forwardRef(() => SuggestedActionsComponent)
2327
- }], viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
2328
- <ng-container *ngIf="actions">
2329
- <span
2330
- #item
2331
- *ngFor="let action of actions; index as index; first as first; last as last"
2332
- class="k-suggestion k-suggestion-primary"
2333
- role="button"
2334
- [class.k-selected]="isSelected(index)"
2335
- [class.k-focus]="isSelected(index)"
2336
- [class.k-active]="isActive(index)"
2337
- [attr.tabindex]="0"
2338
- (click)="actionClick(action, index)"
2339
- (keydown)="actionKeydown($event, action)"
2340
- (mousedown)="toggleActiveState(true, index)"
2341
- (mouseup)="toggleActiveState(false, index)"
2342
- >
2343
- {{ action.title || action.value }}
2344
- </span>
2345
- </ng-container>
2687
+ },
2688
+ SuggestionsScrollService
2689
+ ], viewQueries: [{ propertyName: "suggestionsContainer", first: true, predicate: ["suggestionsContainer"], descendants: true, read: ElementRef }, { propertyName: "prevScrollButton", first: true, predicate: ["prevScrollButton"], descendants: true, read: ElementRef }, { propertyName: "nextScrollButton", first: true, predicate: ["nextScrollButton"], descendants: true, read: ElementRef }, { propertyName: "items", predicate: ["item"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
2690
+ <span
2691
+ *ngIf="hasScrollButtons"
2692
+ #prevScrollButton
2693
+ kendoChatScrollableButton
2694
+ [prev]="true"
2695
+ [title]="getScrollButtonTitle('prev')"
2696
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2697
+ (onClick)="scrollSuggestions($event)"
2698
+ >
2699
+ </span>
2700
+ <div class="k-suggestion-group" *ngIf="hasScrollButtons; else noScrollButtons"
2701
+ #suggestionsContainer
2702
+ role="group"
2703
+ (scroll)="onScroll($event)">
2704
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2705
+ </div>
2706
+
2707
+ <ng-template #noScrollButtons>
2708
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2709
+ </ng-template>
2346
2710
 
2347
- <ng-container *ngIf="suggestions">
2348
- <ng-container *ngIf="!suggestionTemplate?.templateRef">
2711
+ <ng-template #suggestionsContent>
2712
+ <ng-container *ngIf="actions">
2349
2713
  <span
2350
2714
  #item
2351
- *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2352
- class="k-suggestion"
2715
+ *ngFor="let action of actions; index as index; first as first; last as last"
2716
+ class="k-suggestion k-suggestion-primary"
2353
2717
  role="button"
2354
2718
  [class.k-selected]="isSelected(index)"
2355
2719
  [class.k-focus]="isSelected(index)"
2356
2720
  [class.k-active]="isActive(index)"
2357
2721
  [attr.tabindex]="0"
2358
- (click)="suggestionClick(suggestion, index)"
2359
- (keydown)="suggestionKeydown($event, suggestion)"
2722
+ (click)="actionClick(action, index)"
2723
+ (keydown)="actionKeydown($event, action)"
2360
2724
  (mousedown)="toggleActiveState(true, index)"
2361
2725
  (mouseup)="toggleActiveState(false, index)"
2362
2726
  >
2363
- {{ suggestion.text }}
2727
+ {{ action.title || action.value }}
2364
2728
  </span>
2365
2729
  </ng-container>
2366
2730
 
2367
- <ng-container *ngIf="suggestionTemplate?.templateRef">
2368
- <ng-container *ngFor="let suggestion of suggestions">
2369
- <ng-template
2370
- [ngTemplateOutlet]="suggestionTemplate.templateRef"
2371
- [ngTemplateOutletContext]="{ $implicit: suggestion }"
2731
+ <ng-container *ngIf="suggestions">
2732
+ <ng-container *ngIf="!suggestionTemplate?.templateRef">
2733
+ <span
2734
+ #item
2735
+ *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2736
+ class="k-suggestion"
2737
+ role="button"
2738
+ [class.k-selected]="isSelected(index)"
2739
+ [class.k-focus]="isSelected(index)"
2740
+ [class.k-active]="isActive(index)"
2741
+ [attr.tabindex]="0"
2742
+ (click)="suggestionClick(suggestion, index)"
2743
+ (keydown)="suggestionKeydown($event, suggestion)"
2744
+ (mousedown)="toggleActiveState(true, index)"
2745
+ (mouseup)="toggleActiveState(false, index)"
2372
2746
  >
2373
- </ng-template>
2747
+ {{ suggestion.text }}
2748
+ </span>
2749
+ </ng-container>
2750
+
2751
+ <ng-container *ngIf="suggestionTemplate?.templateRef">
2752
+ <ng-container *ngFor="let suggestion of suggestions">
2753
+ <ng-template
2754
+ [ngTemplateOutlet]="suggestionTemplate.templateRef"
2755
+ [ngTemplateOutletContext]="{ $implicit: suggestion }"
2756
+ >
2757
+ </ng-template>
2758
+ </ng-container>
2374
2759
  </ng-container>
2375
2760
  </ng-container>
2376
- </ng-container>
2377
- `, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2761
+ </ng-template>
2762
+
2763
+ <span
2764
+ *ngIf="hasScrollButtons"
2765
+ #nextScrollButton
2766
+ kendoChatScrollableButton
2767
+ [prev]="false"
2768
+ [title]="getScrollButtonTitle('next')"
2769
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2770
+ (onClick)="scrollSuggestions($event)"
2771
+ >
2772
+ </span>
2773
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatScrollableButtonComponent, selector: "[kendoChatScrollableButton]", inputs: ["prev"], outputs: ["onClick"] }] });
2378
2774
  }
2379
2775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, decorators: [{
2380
2776
  type: Component,
@@ -2383,82 +2779,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2383
2779
  providers: [{
2384
2780
  provide: ChatItem,
2385
2781
  useExisting: forwardRef(() => SuggestedActionsComponent)
2386
- }],
2782
+ },
2783
+ SuggestionsScrollService],
2387
2784
  template: `
2388
- <ng-container *ngIf="actions">
2389
- <span
2390
- #item
2391
- *ngFor="let action of actions; index as index; first as first; last as last"
2392
- class="k-suggestion k-suggestion-primary"
2393
- role="button"
2394
- [class.k-selected]="isSelected(index)"
2395
- [class.k-focus]="isSelected(index)"
2396
- [class.k-active]="isActive(index)"
2397
- [attr.tabindex]="0"
2398
- (click)="actionClick(action, index)"
2399
- (keydown)="actionKeydown($event, action)"
2400
- (mousedown)="toggleActiveState(true, index)"
2401
- (mouseup)="toggleActiveState(false, index)"
2402
- >
2403
- {{ action.title || action.value }}
2404
- </span>
2405
- </ng-container>
2785
+ <span
2786
+ *ngIf="hasScrollButtons"
2787
+ #prevScrollButton
2788
+ kendoChatScrollableButton
2789
+ [prev]="true"
2790
+ [title]="getScrollButtonTitle('prev')"
2791
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2792
+ (onClick)="scrollSuggestions($event)"
2793
+ >
2794
+ </span>
2795
+ <div class="k-suggestion-group" *ngIf="hasScrollButtons; else noScrollButtons"
2796
+ #suggestionsContainer
2797
+ role="group"
2798
+ (scroll)="onScroll($event)">
2799
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2800
+ </div>
2406
2801
 
2407
- <ng-container *ngIf="suggestions">
2408
- <ng-container *ngIf="!suggestionTemplate?.templateRef">
2802
+ <ng-template #noScrollButtons>
2803
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2804
+ </ng-template>
2805
+
2806
+ <ng-template #suggestionsContent>
2807
+ <ng-container *ngIf="actions">
2409
2808
  <span
2410
2809
  #item
2411
- *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2412
- class="k-suggestion"
2810
+ *ngFor="let action of actions; index as index; first as first; last as last"
2811
+ class="k-suggestion k-suggestion-primary"
2413
2812
  role="button"
2414
2813
  [class.k-selected]="isSelected(index)"
2415
2814
  [class.k-focus]="isSelected(index)"
2416
2815
  [class.k-active]="isActive(index)"
2417
2816
  [attr.tabindex]="0"
2418
- (click)="suggestionClick(suggestion, index)"
2419
- (keydown)="suggestionKeydown($event, suggestion)"
2817
+ (click)="actionClick(action, index)"
2818
+ (keydown)="actionKeydown($event, action)"
2420
2819
  (mousedown)="toggleActiveState(true, index)"
2421
2820
  (mouseup)="toggleActiveState(false, index)"
2422
2821
  >
2423
- {{ suggestion.text }}
2822
+ {{ action.title || action.value }}
2424
2823
  </span>
2425
2824
  </ng-container>
2426
2825
 
2427
- <ng-container *ngIf="suggestionTemplate?.templateRef">
2428
- <ng-container *ngFor="let suggestion of suggestions">
2429
- <ng-template
2430
- [ngTemplateOutlet]="suggestionTemplate.templateRef"
2431
- [ngTemplateOutletContext]="{ $implicit: suggestion }"
2826
+ <ng-container *ngIf="suggestions">
2827
+ <ng-container *ngIf="!suggestionTemplate?.templateRef">
2828
+ <span
2829
+ #item
2830
+ *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2831
+ class="k-suggestion"
2832
+ role="button"
2833
+ [class.k-selected]="isSelected(index)"
2834
+ [class.k-focus]="isSelected(index)"
2835
+ [class.k-active]="isActive(index)"
2836
+ [attr.tabindex]="0"
2837
+ (click)="suggestionClick(suggestion, index)"
2838
+ (keydown)="suggestionKeydown($event, suggestion)"
2839
+ (mousedown)="toggleActiveState(true, index)"
2840
+ (mouseup)="toggleActiveState(false, index)"
2432
2841
  >
2433
- </ng-template>
2842
+ {{ suggestion.text }}
2843
+ </span>
2844
+ </ng-container>
2845
+
2846
+ <ng-container *ngIf="suggestionTemplate?.templateRef">
2847
+ <ng-container *ngFor="let suggestion of suggestions">
2848
+ <ng-template
2849
+ [ngTemplateOutlet]="suggestionTemplate.templateRef"
2850
+ [ngTemplateOutletContext]="{ $implicit: suggestion }"
2851
+ >
2852
+ </ng-template>
2853
+ </ng-container>
2434
2854
  </ng-container>
2435
2855
  </ng-container>
2436
- </ng-container>
2856
+ </ng-template>
2857
+
2858
+ <span
2859
+ *ngIf="hasScrollButtons"
2860
+ #nextScrollButton
2861
+ kendoChatScrollableButton
2862
+ [prev]="false"
2863
+ [title]="getScrollButtonTitle('next')"
2864
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2865
+ (onClick)="scrollSuggestions($event)"
2866
+ >
2867
+ </span>
2437
2868
  `,
2438
2869
  standalone: true,
2439
- imports: [NgFor, NgIf, NgTemplateOutlet]
2870
+ imports: [NgFor, NgIf, NgTemplateOutlet, ChatScrollableButtonComponent]
2440
2871
  }]
2441
- }], propDecorators: { actions: [{
2872
+ }], ctorParameters: () => [{ type: ChatService }, { type: i1.LocalizationService }, { type: SuggestionsScrollService }, { type: i0.NgZone }, { type: i0.Renderer2 }], propDecorators: { defaultClass: [{
2873
+ type: HostBinding,
2874
+ args: ['class.k-suggestion-group']
2875
+ }], scrollableClass: [{
2876
+ type: HostBinding,
2877
+ args: ['class.k-suggestion-group-scrollable']
2878
+ }], scrollButtonsClass: [{
2879
+ type: HostBinding,
2880
+ args: ['class.k-suggestion-scrollwrap']
2881
+ }], role: [{
2882
+ type: HostBinding,
2883
+ args: ['attr.role']
2884
+ }], actions: [{
2442
2885
  type: Input
2443
2886
  }], suggestions: [{
2444
2887
  type: Input
2445
2888
  }], tabbable: [{
2446
2889
  type: Input
2890
+ }], type: [{
2891
+ type: Input
2447
2892
  }], suggestionTemplate: [{
2448
2893
  type: Input
2449
2894
  }], dispatchAction: [{
2450
2895
  type: Output
2451
2896
  }], dispatchSuggestion: [{
2452
2897
  type: Output
2453
- }], defaultClass: [{
2454
- type: HostBinding,
2455
- args: ['class.k-suggestion-group']
2456
- }], role: [{
2457
- type: HostBinding,
2458
- args: ['attr.role']
2459
2898
  }], items: [{
2460
2899
  type: ViewChildren,
2461
2900
  args: ['item']
2901
+ }], suggestionsContainer: [{
2902
+ type: ViewChild,
2903
+ args: ['suggestionsContainer', { read: ElementRef, static: false }]
2904
+ }], prevScrollButton: [{
2905
+ type: ViewChild,
2906
+ args: ['prevScrollButton', { read: ElementRef }]
2907
+ }], nextScrollButton: [{
2908
+ type: ViewChild,
2909
+ args: ['nextScrollButton', { read: ElementRef }]
2462
2910
  }] } });
2463
2911
 
2464
2912
  /* eslint-disable @typescript-eslint/no-empty-function */
@@ -2499,7 +2947,7 @@ class ChatFileComponent extends ChatItem {
2499
2947
  useExisting: forwardRef(() => ChatFileComponent)
2500
2948
  }], usesInheritance: true, ngImport: i0, template: `
2501
2949
  <kendo-icon-wrapper
2502
- size="xxlarge"
2950
+ size="xlarge"
2503
2951
  [name]="fileGroupClass(chatFile.extension)"
2504
2952
  [svgIcon]="fileThumbnail(chatFile.extension)"
2505
2953
  >
@@ -2540,7 +2988,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2540
2988
  }],
2541
2989
  template: `
2542
2990
  <kendo-icon-wrapper
2543
- size="xxlarge"
2991
+ size="xlarge"
2544
2992
  [name]="fileGroupClass(chatFile.extension)"
2545
2993
  [svgIcon]="fileThumbnail(chatFile.extension)"
2546
2994
  >
@@ -2790,6 +3238,7 @@ class MessageBoxComponent {
2790
3238
  return;
2791
3239
  }
2792
3240
  const message = {
3241
+ id: guid(),
2793
3242
  text: this.inputValue,
2794
3243
  timestamp: new Date(),
2795
3244
  author: { id: this.authorId },
@@ -2927,6 +3376,7 @@ class MessageBoxComponent {
2927
3376
  *ngIf="suggestions?.length > 0"
2928
3377
  #suggestedActions
2929
3378
  [suggestions]="suggestions"
3379
+ type="suggestion"
2930
3380
  [suggestionTemplate]="suggestionTemplate"
2931
3381
  [tabbable]="true"
2932
3382
  (dispatchSuggestion)="dispatchSuggestion($event)"
@@ -3027,7 +3477,7 @@ class MessageBoxComponent {
3027
3477
  [showFileList]="false"
3028
3478
  (select)="handleFileSelect($event)"
3029
3479
  ></kendo-fileselect>
3030
- `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: TextAreaPrefixComponent, selector: "kendo-textarea-prefix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaPrefix"] }, { kind: "component", type: SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }, { kind: "component", type: InputSpacerComponent, selector: "kendo-input-spacer, kendo-textbox-spacer", inputs: ["width"] }, { kind: "component", type: FileSelectComponent, selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }] });
3480
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: TextAreaPrefixComponent, selector: "kendo-textarea-prefix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaPrefix"] }, { kind: "component", type: SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }, { kind: "component", type: InputSpacerComponent, selector: "kendo-input-spacer, kendo-textbox-spacer", inputs: ["width"] }, { kind: "component", type: FileSelectComponent, selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "type", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }] });
3031
3481
  }
3032
3482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBoxComponent, decorators: [{
3033
3483
  type: Component,
@@ -3038,6 +3488,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3038
3488
  *ngIf="suggestions?.length > 0"
3039
3489
  #suggestedActions
3040
3490
  [suggestions]="suggestions"
3491
+ type="suggestion"
3041
3492
  [suggestionTemplate]="suggestionTemplate"
3042
3493
  [tabbable]="true"
3043
3494
  (dispatchSuggestion)="dispatchSuggestion($event)"
@@ -3183,6 +3634,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3183
3634
  type: Output
3184
3635
  }] } });
3185
3636
 
3637
+ /**
3638
+ * Defines a template for displaying custom content inside the Chat messages.
3639
+ *
3640
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageContentTemplate` directive inside the `<kendo-chat>` component.
3641
+ * The template context is set to the `message` instance.
3642
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3643
+ *
3644
+ * @example
3645
+ * ```html
3646
+ * <kendo-chat>
3647
+ * <ng-template kendoChatMessageContentTemplate let-message>
3648
+ * <div>Message: {{ message.text }}</div>
3649
+ * </ng-template>
3650
+ * </kendo-chat>
3651
+ * ```
3652
+ */
3653
+ class MessageContentTemplateDirective {
3654
+ templateRef;
3655
+ constructor(templateRef) {
3656
+ this.templateRef = templateRef;
3657
+ }
3658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageContentTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3659
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatMessageContentTemplate]", ngImport: i0 });
3660
+ }
3661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageContentTemplateDirective, decorators: [{
3662
+ type: Directive,
3663
+ args: [{
3664
+ selector: '[kendoChatMessageContentTemplate]',
3665
+ standalone: true
3666
+ }]
3667
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3668
+ type: Optional
3669
+ }] }] });
3670
+
3186
3671
  /**
3187
3672
  * Defines a template for displaying the chat timestamp.
3188
3673
  *
@@ -3248,6 +3733,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3248
3733
  }]
3249
3734
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
3250
3735
 
3736
+ /**
3737
+ * Defines a template for displaying fully custom Chat message bubbles.
3738
+ *
3739
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageTemplate` directive inside the `<kendo-chat>` component.
3740
+ * The template context is set to the `message` instance.
3741
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3742
+ *
3743
+ * @example
3744
+ * ```html
3745
+ * <kendo-chat>
3746
+ * <ng-template kendoChatMessageTemplate let-message>
3747
+ * <div>Message: {{ message.text }}</div>
3748
+ * </ng-template>
3749
+ * </kendo-chat>
3750
+ * ```
3751
+ */
3752
+ class MessageTemplateDirective {
3753
+ templateRef;
3754
+ constructor(templateRef) {
3755
+ this.templateRef = templateRef;
3756
+ }
3757
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3758
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageTemplateDirective, isStandalone: true, selector: "[kendoChatMessageTemplate]", ngImport: i0 });
3759
+ }
3760
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageTemplateDirective, decorators: [{
3761
+ type: Directive,
3762
+ args: [{
3763
+ selector: '[kendoChatMessageTemplate]',
3764
+ standalone: true
3765
+ }]
3766
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3767
+ type: Optional
3768
+ }] }] });
3769
+
3770
+ /**
3771
+ * Defines a template for displaying custom content inside the current user's messages in the Chat.
3772
+ *
3773
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatAuthorMessageContentTemplate` directive inside the `<kendo-chat>` component.
3774
+ * The template context is set to the `message` instance.
3775
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3776
+ *
3777
+ * @example
3778
+ * ```html
3779
+ * <kendo-chat>
3780
+ * <ng-template kendoChatAuthorMessageContentTemplate let-message>
3781
+ * <div>Message: {{ message.text }}</div>
3782
+ * </ng-template>
3783
+ * </kendo-chat>
3784
+ * ```
3785
+ */
3786
+ class AuthorMessageContentTemplateDirective {
3787
+ templateRef;
3788
+ constructor(templateRef) {
3789
+ this.templateRef = templateRef;
3790
+ }
3791
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageContentTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3792
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AuthorMessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatAuthorMessageContentTemplate]", ngImport: i0 });
3793
+ }
3794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageContentTemplateDirective, decorators: [{
3795
+ type: Directive,
3796
+ args: [{
3797
+ selector: '[kendoChatAuthorMessageContentTemplate]',
3798
+ standalone: true
3799
+ }]
3800
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3801
+ type: Optional
3802
+ }] }] });
3803
+
3804
+ /**
3805
+ * Defines a template for displaying custom content inside the other users' messages in the Chat.
3806
+ *
3807
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatReceiverMessageContentTemplate` directive inside the `<kendo-chat>` component.
3808
+ * The template context is set to the `message` instance.
3809
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3810
+ *
3811
+ * @example
3812
+ * ```html
3813
+ * <kendo-chat>
3814
+ * <ng-template kendoChatReceiverMessageContentTemplate let-message>
3815
+ * <div>Message: {{ message.text }}</div>
3816
+ * </ng-template>
3817
+ * </kendo-chat>
3818
+ * ```
3819
+ */
3820
+ class ReceiverMessageContentTemplateDirective {
3821
+ templateRef;
3822
+ constructor(templateRef) {
3823
+ this.templateRef = templateRef;
3824
+ }
3825
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageContentTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3826
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ReceiverMessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatReceiverMessageContentTemplate]", ngImport: i0 });
3827
+ }
3828
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageContentTemplateDirective, decorators: [{
3829
+ type: Directive,
3830
+ args: [{
3831
+ selector: '[kendoChatReceiverMessageContentTemplate]',
3832
+ standalone: true
3833
+ }]
3834
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3835
+ type: Optional
3836
+ }] }] });
3837
+
3838
+ /**
3839
+ * Defines a template for displaying fully custom Chat message bubbles for the other users.
3840
+ *
3841
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatReceiverMessageTemplate` directive inside the `<kendo-chat>` component.
3842
+ * The template context is set to the `message` instance.
3843
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3844
+ *
3845
+ * @example
3846
+ * ```html
3847
+ * <kendo-chat>
3848
+ * <ng-template kendoChatReceiverMessageTemplate let-message>
3849
+ * <div>Message: {{ message.text }}</div>
3850
+ * </ng-template>
3851
+ * </kendo-chat>
3852
+ * ```
3853
+ */
3854
+ class ReceiverMessageTemplateDirective {
3855
+ templateRef;
3856
+ constructor(templateRef) {
3857
+ this.templateRef = templateRef;
3858
+ }
3859
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3860
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ReceiverMessageTemplateDirective, isStandalone: true, selector: "[kendoChatReceiverMessageTemplate]", ngImport: i0 });
3861
+ }
3862
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageTemplateDirective, decorators: [{
3863
+ type: Directive,
3864
+ args: [{
3865
+ selector: '[kendoChatReceiverMessageTemplate]',
3866
+ standalone: true
3867
+ }]
3868
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3869
+ type: Optional
3870
+ }] }] });
3871
+
3872
+ /**
3873
+ * Defines a template for displaying fully custom Chat message bubbles for the current user.
3874
+ *
3875
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatAuthorMessageTemplate` directive inside the `<kendo-chat>` component.
3876
+ * The template context is set to the `message` instance.
3877
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3878
+ *
3879
+ * @example
3880
+ * ```html
3881
+ * <kendo-chat>
3882
+ * <ng-template kendoChatAuthorMessageTemplate let-message>
3883
+ * <div>Message: {{ message.text }}</div>
3884
+ * </ng-template>
3885
+ * </kendo-chat>
3886
+ * ```
3887
+ */
3888
+ class AuthorMessageTemplateDirective {
3889
+ templateRef;
3890
+ constructor(templateRef) {
3891
+ this.templateRef = templateRef;
3892
+ }
3893
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
3894
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AuthorMessageTemplateDirective, isStandalone: true, selector: "[kendoChatAuthorMessageTemplate]", ngImport: i0 });
3895
+ }
3896
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageTemplateDirective, decorators: [{
3897
+ type: Directive,
3898
+ args: [{
3899
+ selector: '[kendoChatAuthorMessageTemplate]',
3900
+ standalone: true
3901
+ }]
3902
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3903
+ type: Optional
3904
+ }] }] });
3905
+
3251
3906
  // eslint-disable no-forward-ref
3252
3907
  /**
3253
3908
  * @hidden
@@ -3257,13 +3912,23 @@ class MessageComponent extends ChatItem {
3257
3912
  intl;
3258
3913
  chatService;
3259
3914
  localization;
3260
- message;
3915
+ cdr;
3916
+ set message(value) {
3917
+ this._message = value;
3918
+ }
3919
+ get message() {
3920
+ return this._message;
3921
+ }
3261
3922
  tabbable;
3262
- template;
3923
+ authorMessageContentTemplate;
3924
+ receiverMessageContentTemplate;
3925
+ messageContentTemplate;
3926
+ authorMessageTemplate;
3927
+ receiverMessageTemplate;
3928
+ messageTemplate;
3263
3929
  statusTemplate;
3264
3930
  showMessageTime = true;
3265
3931
  authorId;
3266
- contextMenuVisibilityChange = new EventEmitter();
3267
3932
  cssClass = true;
3268
3933
  get removedClass() {
3269
3934
  return this.message.isDeleted;
@@ -3278,44 +3943,142 @@ class MessageComponent extends ChatItem {
3278
3943
  this.onExpandableKeydown(event);
3279
3944
  }
3280
3945
  selected;
3281
- active = false;
3282
- selectOnMenuClose = false;
3283
3946
  get tabIndex() {
3284
3947
  return this.tabbable ? '0' : '-1';
3285
3948
  }
3286
- onContextMenu(event) {
3287
- if (this.message.isDeleted) {
3288
- return;
3289
- }
3290
- this.active = true;
3291
- event.stopPropagation();
3292
- }
3293
3949
  expandIcon = chevronDownIcon;
3294
3950
  collapseIcon = chevronUpIcon;
3295
3951
  downloadIcon = downloadIcon;
3296
3952
  isMessageExpanded = false;
3297
- contextMenuActions = [];
3953
+ showExpandCollapseIcon = false;
3298
3954
  fileActions = [];
3955
+ toolbarActions = [];
3299
3956
  parts = [];
3300
- constructor(element, intl, chatService, localization) {
3957
+ get useCustomBubbleTemplate() {
3958
+ return !!(this.getActiveBubbleTemplate());
3959
+ }
3960
+ get useCustomContentTemplate() {
3961
+ return !!(this.getActiveContentTemplate());
3962
+ }
3963
+ get hasMessageContent() {
3964
+ return !!(this.message?.text || this.message?.files?.length > 0);
3965
+ }
3966
+ get hasFiles() {
3967
+ return this.message?.files?.length > 0;
3968
+ }
3969
+ get hasMultipleFiles() {
3970
+ return this.message?.files?.length > 1;
3971
+ }
3972
+ get isActiveMessage() {
3973
+ return this.chatService.active && this.message?.id === this.chatService.activeMessage?.id;
3974
+ }
3975
+ get isMessageExpandable() {
3976
+ const isOwn = this.isOwnMessage(this.message);
3977
+ const messageSettings = isOwn
3978
+ ? this.chatService.authorMessageSettings
3979
+ : this.chatService.receiverMessageSettings;
3980
+ if (isPresent(messageSettings?.allowMessageCollapse)) {
3981
+ return messageSettings.allowMessageCollapse;
3982
+ }
3983
+ return this.chatService.allowMessageCollapse || false;
3984
+ }
3985
+ get showToolbar() {
3986
+ if (this.message?.isDeleted) {
3987
+ return false;
3988
+ }
3989
+ const hasComponentActions = this.chatService.messageToolbarActions?.length > 0;
3990
+ const hasMessageActions = this.toolbarActions?.length > 0;
3991
+ return hasComponentActions || hasMessageActions;
3992
+ }
3993
+ subs = new Subscription();
3994
+ _message;
3995
+ constructor(element, intl, chatService, localization, cdr) {
3301
3996
  super();
3302
3997
  this.element = element;
3303
3998
  this.intl = intl;
3304
3999
  this.chatService = chatService;
3305
4000
  this.localization = localization;
4001
+ this.cdr = cdr;
3306
4002
  }
3307
4003
  ngOnInit() {
3308
- this.contextMenuActions = this.transformActions(this.chatService.messageContextMenuActions);
3309
- this.fileActions = this.transformActions(this.chatService.fileActions);
4004
+ this.fileActions = this.getFileActions();
4005
+ this.toolbarActions = this.getToolbarActions();
4006
+ const settingsChange$ = this.isOwnMessage(this.message)
4007
+ ? this.chatService.authorMessageSettingsChange$
4008
+ : this.chatService.receiverMessageSettingsChange$;
4009
+ this.subs.add(settingsChange$.subscribe(() => {
4010
+ this.fileActions = this.getFileActions();
4011
+ this.toolbarActions = this.getToolbarActions();
4012
+ setTimeout(() => {
4013
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4014
+ });
4015
+ }));
4016
+ this.subs.add(this.chatService.allowMessageCollapseChange$.subscribe(() => {
4017
+ setTimeout(() => {
4018
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4019
+ });
4020
+ }));
3310
4021
  if (this.message.id) {
3311
4022
  this.chatService.registerMessageElement(this.message.id, this.element);
3312
4023
  }
3313
4024
  this.parts = this.getFormattedTextParts(this.message.text);
3314
4025
  }
4026
+ ngAfterViewInit() {
4027
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4028
+ this.cdr.detectChanges();
4029
+ }
3315
4030
  ngOnDestroy() {
3316
4031
  if (this.message.id) {
3317
4032
  this.chatService.unregisterMessageElement(this.message.id);
3318
4033
  }
4034
+ this.subs.unsubscribe();
4035
+ }
4036
+ calculateExpandCollapseIconVisibility() {
4037
+ if (this.isMessageExpanded) {
4038
+ return true;
4039
+ }
4040
+ const bubbleContent = this.element.nativeElement.querySelector('.k-bubble-content');
4041
+ if (!bubbleContent) {
4042
+ return false;
4043
+ }
4044
+ const hasVerticalOverflow = bubbleContent.scrollHeight > bubbleContent.clientHeight;
4045
+ const hasHorizontalOverflow = bubbleContent.scrollWidth > bubbleContent.clientWidth;
4046
+ if (this.useCustomContentTemplate) {
4047
+ return hasVerticalOverflow || hasHorizontalOverflow;
4048
+ }
4049
+ const messageText = this.element.nativeElement.querySelector('.k-chat-bubble-text');
4050
+ const hasTextOverflow = messageText?.scrollWidth > messageText?.clientWidth;
4051
+ return hasTextOverflow || hasVerticalOverflow || hasHorizontalOverflow;
4052
+ }
4053
+ getActiveBubbleTemplate() {
4054
+ const isOwn = this.isOwnMessage(this.message);
4055
+ if (isOwn && this.authorMessageTemplate) {
4056
+ return this.authorMessageTemplate;
4057
+ }
4058
+ if (!isOwn && this.receiverMessageTemplate) {
4059
+ return this.receiverMessageTemplate;
4060
+ }
4061
+ if (this.messageTemplate) {
4062
+ return this.messageTemplate;
4063
+ }
4064
+ return null;
4065
+ }
4066
+ getActiveContentTemplate() {
4067
+ const isOwn = this.isOwnMessage(this.message);
4068
+ if (isOwn && this.authorMessageContentTemplate) {
4069
+ return this.authorMessageContentTemplate;
4070
+ }
4071
+ if (!isOwn && this.receiverMessageContentTemplate) {
4072
+ return this.receiverMessageContentTemplate;
4073
+ }
4074
+ if (this.messageContentTemplate) {
4075
+ return this.messageContentTemplate;
4076
+ }
4077
+ return null;
4078
+ }
4079
+ getDeletedMessageText() {
4080
+ const isOwn = this.isOwnMessage(this.message);
4081
+ return isOwn ? this.textFor('deletedMessageSenderText') : this.textFor('deletedMessageReceiverText');
3319
4082
  }
3320
4083
  textFor(key) {
3321
4084
  return this.localization.get(key);
@@ -3343,66 +4106,19 @@ class MessageComponent extends ChatItem {
3343
4106
  this.toggleMessageState(event);
3344
4107
  }
3345
4108
  }
3346
- isToolbarVisible() {
3347
- if (!this.chatService.messageToolbarActions || this.chatService.messageToolbarActions.length === 0 || this.message.isDeleted) {
3348
- return false;
3349
- }
3350
- return this.chatService.messageToolbarVisibility === 'always';
3351
- }
3352
4109
  onToolbarAction(event, action, message) {
3353
4110
  event.stopImmediatePropagation();
3354
4111
  this.chatService.emit('toolbarAction', { action, message });
3355
4112
  }
3356
- onContextMenuAction(action) {
3357
- if (action.id === 'reply') {
3358
- this.chatService.reply = this.message;
3359
- }
3360
- if (action.id === 'copy') {
3361
- navigator.clipboard.writeText(this.message.text);
3362
- }
3363
- this.chatService.emit('contextMenuAction', { action, message: this.message });
3364
- }
3365
4113
  onFileAction(action, file) {
3366
4114
  if (action.originalAction.id === 'download') {
3367
4115
  this.chatService.emit('fileDownload', { files: [file], message: this.message });
3368
4116
  }
3369
4117
  this.chatService.emit('fileAction', { action: action.originalAction, file });
3370
4118
  }
3371
- transformActions(actions) {
3372
- return actions.map(action => ({
3373
- text: action.label,
3374
- icon: action.icon,
3375
- svgIcon: action.svgIcon,
3376
- disabled: action.disabled,
3377
- originalAction: action
3378
- }));
3379
- }
3380
4119
  getMessageById(id) {
3381
4120
  return this.chatService.getMessageById(id);
3382
4121
  }
3383
- // processes the message text to extract parts that are either plain text or links
3384
- // this allows us not to render an additional wrapper element for the text
3385
- getFormattedTextParts(text) {
3386
- if (!text) {
3387
- return [];
3388
- }
3389
- const parts = [];
3390
- const urlMatches = Array.from(text.matchAll(URL_REGEX));
3391
- let lastIndex = 0;
3392
- for (const match of urlMatches) {
3393
- const url = match[1];
3394
- const matchStart = match.index;
3395
- if (matchStart > lastIndex) {
3396
- parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
3397
- }
3398
- parts.push({ type: 'link', content: url, href: url });
3399
- lastIndex = matchStart + match[0].length;
3400
- }
3401
- if (lastIndex < text.length) {
3402
- parts.push({ type: 'text', content: text.substring(lastIndex) });
3403
- }
3404
- return parts;
3405
- }
3406
4122
  onReplyReferenceClick(event, replyToId) {
3407
4123
  event.stopPropagation();
3408
4124
  this.chatService.emit('replyReferenceClick', replyToId);
@@ -3414,9 +4130,9 @@ class MessageComponent extends ChatItem {
3414
4130
  this.onActionButtonClick(originalEvent);
3415
4131
  }
3416
4132
  }
3417
- this.active = false;
3418
- this.contextMenuVisibilityChange.emit(false);
3419
- if (this.selectOnMenuClose) {
4133
+ this.chatService.active = false;
4134
+ this.chatService.emit('contextMenuVisibilityChange', false);
4135
+ if (this.chatService.selectOnMenuClose) {
3420
4136
  this.selected = true;
3421
4137
  this.focus();
3422
4138
  }
@@ -3425,16 +4141,16 @@ class MessageComponent extends ChatItem {
3425
4141
  const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
3426
4142
  const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
3427
4143
  if (clickOutsideMessage && !menuItemClick) {
3428
- this.selectOnMenuClose = false;
4144
+ this.chatService.selectOnMenuClose = false;
3429
4145
  }
3430
4146
  }
3431
4147
  handleMenuOpen() {
3432
- this.selectOnMenuClose = this.selected;
3433
- this.contextMenuVisibilityChange.emit(true);
4148
+ this.chatService.selectOnMenuClose = this.selected;
4149
+ this.chatService.emit('contextMenuVisibilityChange', true);
3434
4150
  }
3435
4151
  onActionPopupChange(expanded) {
3436
4152
  if (expanded) {
3437
- this.active = true;
4153
+ this.chatService.active = true;
3438
4154
  this.handleMenuOpen();
3439
4155
  }
3440
4156
  else {
@@ -3444,124 +4160,211 @@ class MessageComponent extends ChatItem {
3444
4160
  isOwnMessage(msg) {
3445
4161
  return isAuthor(this.authorId, msg);
3446
4162
  }
3447
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.IntlService }, { token: ChatService }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
3448
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", template: "template", statusTemplate: "statusTemplate", showMessageTime: "showMessageTime", authorId: "authorId" }, outputs: { contextMenuVisibilityChange: "contextMenuVisibilityChange" }, host: { listeners: { "keydown": "onKeyDown($event)", "contextmenu": "onContextMenu($event)" }, properties: { "class.k-message": "this.cssClass", "class.k-message-removed": "this.removedClass", "attr.tabIndex": "this.tabIndex" } }, providers: [
4163
+ getFormattedTextParts(text) {
4164
+ if (!text) {
4165
+ return [];
4166
+ }
4167
+ const parts = [];
4168
+ const urlMatches = Array.from(text.matchAll(URL_REGEX));
4169
+ let lastIndex = 0;
4170
+ for (const match of urlMatches) {
4171
+ const url = match[1];
4172
+ const matchStart = match.index;
4173
+ if (!isPresent(matchStart)) {
4174
+ continue;
4175
+ }
4176
+ if (matchStart > lastIndex) {
4177
+ parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
4178
+ }
4179
+ parts.push({ type: 'link', content: url, href: url });
4180
+ lastIndex = matchStart + match[0].length;
4181
+ }
4182
+ if (lastIndex < text.length) {
4183
+ parts.push({ type: 'text', content: text.substring(lastIndex) });
4184
+ }
4185
+ return parts;
4186
+ }
4187
+ getMessageSettings() {
4188
+ return this.isOwnMessage(this.message)
4189
+ ? this.chatService.authorMessageSettings
4190
+ : this.chatService.receiverMessageSettings;
4191
+ }
4192
+ getToolbarActions() {
4193
+ const messageSettings = this.getMessageSettings();
4194
+ return messageSettings?.messageToolbarActions?.length
4195
+ ? messageSettings.messageToolbarActions
4196
+ : this.chatService.messageToolbarActions || [];
4197
+ }
4198
+ getFileActions() {
4199
+ const messageSettings = this.getMessageSettings();
4200
+ const actions = messageSettings?.fileActions?.length
4201
+ ? messageSettings.fileActions
4202
+ : this.chatService.fileActions || [];
4203
+ return transformActions(actions);
4204
+ }
4205
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.IntlService }, { token: ChatService }, { token: i1.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4206
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", authorMessageContentTemplate: "authorMessageContentTemplate", receiverMessageContentTemplate: "receiverMessageContentTemplate", messageContentTemplate: "messageContentTemplate", authorMessageTemplate: "authorMessageTemplate", receiverMessageTemplate: "receiverMessageTemplate", messageTemplate: "messageTemplate", statusTemplate: "statusTemplate", showMessageTime: "showMessageTime", authorId: "authorId" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "class.k-message": "this.cssClass", "class.k-message-removed": "this.removedClass", "attr.tabIndex": "this.tabIndex" } }, providers: [
3449
4207
  {
3450
4208
  provide: ChatItem,
3451
4209
  useExisting: forwardRef(() => MessageComponent)
3452
4210
  }
3453
4211
  ], usesInheritance: true, ngImport: i0, template: `
3454
- <time
3455
- [attr.aria-hidden]="!selected"
3456
- class="k-message-time"
3457
- *ngIf="message.timestamp"
3458
- >
3459
- {{ formatTimeStamp(message.timestamp) }}
3460
- </time>
3461
-
3462
- <ng-container *ngIf="!message.typing; else typing">
3463
- <div
3464
- class="k-chat-bubble k-bubble"
3465
- *ngIf="template"
3466
- [attr.tabindex]="0"
3467
- [ngClass]="{
3468
- 'k-selected': selected,
3469
- 'k-focus': selected,
3470
- 'k-active': active
3471
- }"
3472
- >
3473
- <div class="k-bubble-content">
3474
- <ng-container
3475
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
3476
- >
3477
- </ng-container>
3478
- </div>
3479
- </div>
4212
+ <ng-container *ngIf="useCustomBubbleTemplate">
4213
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4214
+ </ng-container>
3480
4215
 
3481
- <div
3482
- class="k-chat-bubble k-bubble"
3483
- *ngIf="!template && (message.text || message.files)"
3484
- [attr.tabindex]="0"
3485
- [ngClass]="{
3486
- 'k-bubble-expandable': chatService.allowMessageCollapse,
3487
- 'k-expanded': isMessageExpanded,
3488
- 'k-selected': selected,
3489
- 'k-focus': selected,
3490
- 'k-active': active
3491
- }"
4216
+ <ng-container *ngIf="!useCustomBubbleTemplate">
4217
+ <time
4218
+ [attr.aria-hidden]="!selected"
4219
+ class="k-message-time"
4220
+ *ngIf="chatService.timestampVisibility === 'focus' && message.timestamp"
3492
4221
  >
3493
- <div class="k-bubble-content">
3494
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
3495
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
3496
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
3497
- </div>
3498
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
3499
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
3500
- <ng-container *ngIf="!message.isDeleted && parts.length">
3501
- <ng-container *ngFor="let part of parts">
3502
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
3503
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
3504
- </ng-container>
3505
- </ng-container>
3506
- </span>
3507
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
3508
- <li
3509
- *ngFor="let file of message.files"
3510
- class="k-chat-file"
3511
- [chatFile]="file"
3512
- [fileActions]="fileActions"
3513
- (actionClick)="onFileAction($event, file)"
3514
- (actionsToggle)="onActionPopupChange($event)"
3515
- (actionButtonClick)="onActionButtonClick($event)"
3516
- ></li>
3517
- </ul>
3518
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
3519
- <button
3520
- kendoButton
3521
- class="k-chat-download-button"
3522
- fillMode="flat"
3523
- icon="download"
3524
- [svgIcon]="downloadIcon"
3525
- (click)="onDownloadAll()"
3526
- >{{ textFor('downloadAllFilesText') }}</button>
4222
+ {{ formatTimeStamp(message.timestamp) }}
4223
+ </time>
4224
+
4225
+ <ng-container *ngIf="message.typing">
4226
+ <div class="k-chat-bubble k-bubble">
4227
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
4228
+ <span></span>
4229
+ <span></span>
4230
+ <span></span>
3527
4231
  </div>
3528
4232
  </div>
3529
- <span
3530
- class="k-bubble-expandable-indicator"
3531
- *ngIf="chatService.allowMessageCollapse"
3532
- [attr.role]="'button'"
3533
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
3534
- (mousedown)="chatService.toggleMessageState = true"
3535
- (click)="toggleMessageState($event)"
4233
+ </ng-container>
4234
+
4235
+ <ng-container *ngIf="!message.typing">
4236
+ <div
4237
+ class="k-chat-bubble k-bubble"
4238
+ *ngIf="useCustomContentTemplate"
4239
+ [attr.tabindex]="0"
4240
+ [ngClass]="{
4241
+ 'k-bubble-expandable': isMessageExpandable,
4242
+ 'k-expanded': isMessageExpanded,
4243
+ 'k-selected': selected,
4244
+ 'k-focus': selected,
4245
+ 'k-active': isActiveMessage
4246
+ }"
3536
4247
  >
3537
- <kendo-icon-wrapper
3538
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
3539
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4248
+ <div class="k-bubble-content">
4249
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4250
+ </div>
4251
+ <span
4252
+ class="k-bubble-expandable-indicator"
4253
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4254
+ [attr.tabindex]="'0'"
4255
+ [attr.role]="'button'"
4256
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4257
+ (mousedown)="chatService.toggleMessageState = true"
4258
+ (click)="toggleMessageState($event)"
3540
4259
  >
3541
- </kendo-icon-wrapper>
3542
- </span>
3543
- </div>
3544
- </ng-container>
4260
+ <kendo-icon-wrapper
4261
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4262
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4263
+ >
4264
+ </kendo-icon-wrapper>
4265
+ </span>
4266
+ </div>
3545
4267
 
3546
- <span
3547
- class="k-message-status"
3548
- *ngIf="message.status"
3549
- >
3550
- <ng-container *ngIf="statusTemplate?.templateRef">
3551
- <ng-template
3552
- [ngTemplateOutlet]="statusTemplate.templateRef"
3553
- [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4268
+ <div
4269
+ class="k-chat-bubble k-bubble"
4270
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
4271
+ [attr.tabindex]="0"
4272
+ [ngClass]="{
4273
+ 'k-bubble-expandable': isMessageExpandable,
4274
+ 'k-expanded': isMessageExpanded,
4275
+ 'k-selected': selected,
4276
+ 'k-focus': selected,
4277
+ 'k-active': isActiveMessage
4278
+ }"
3554
4279
  >
3555
- </ng-template>
3556
- </ng-container>
3557
- <ng-container *ngIf="!statusTemplate?.templateRef">
3558
- {{ message.status }}
4280
+ <div class="k-bubble-content">
4281
+ <ng-container *ngIf="message.text || message.isDeleted">
4282
+ <div
4283
+ class="k-message-reference k-message-reference-receiver"
4284
+ *ngIf="message.replyToId && !message.isDeleted"
4285
+ (click)="onReplyReferenceClick($event, message.replyToId)"
4286
+ >
4287
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
4288
+ </div>
4289
+
4290
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
4291
+ {{ getDeletedMessageText() }}
4292
+ </span>
4293
+
4294
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
4295
+ <ng-container *ngFor="let part of parts">
4296
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
4297
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
4298
+ </ng-container>
4299
+ </span>
4300
+ </ng-container>
4301
+
4302
+ <ul
4303
+ class="k-chat-file-wrapper"
4304
+ *ngIf="hasFiles && !message.isDeleted"
4305
+ [ngClass]="{
4306
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
4307
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
4308
+ }"
4309
+ >
4310
+ <li
4311
+ *ngFor="let file of message.files"
4312
+ class="k-chat-file"
4313
+ [chatFile]="file"
4314
+ [fileActions]="fileActions"
4315
+ (actionClick)="onFileAction($event, file)"
4316
+ (actionsToggle)="onActionPopupChange($event)"
4317
+ (actionButtonClick)="onActionButtonClick($event)"
4318
+ ></li>
4319
+ </ul>
4320
+
4321
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
4322
+ <button
4323
+ kendoButton
4324
+ class="k-chat-download-button"
4325
+ fillMode="flat"
4326
+ icon="download"
4327
+ [svgIcon]="downloadIcon"
4328
+ [attr.title]="textFor('downloadAllFilesText')"
4329
+ (click)="onDownloadAll()"
4330
+ >{{ textFor('downloadAllFilesText') }}</button>
4331
+ </div>
4332
+ </div>
4333
+
4334
+ <span
4335
+ class="k-bubble-expandable-indicator"
4336
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4337
+ [attr.tabindex]="'0'"
4338
+ [attr.role]="'button'"
4339
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4340
+ (mousedown)="chatService.toggleMessageState = true"
4341
+ (click)="toggleMessageState($event)"
4342
+ >
4343
+ <kendo-icon-wrapper
4344
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4345
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4346
+ >
4347
+ </kendo-icon-wrapper>
4348
+ </span>
4349
+ </div>
3559
4350
  </ng-container>
3560
- </span>
3561
4351
 
3562
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
4352
+ <span class="k-message-status" *ngIf="message.status">
4353
+ <ng-container *ngIf="statusTemplate?.templateRef">
4354
+ <ng-template
4355
+ [ngTemplateOutlet]="statusTemplate.templateRef"
4356
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4357
+ >
4358
+ </ng-template>
4359
+ </ng-container>
4360
+ <ng-container *ngIf="!statusTemplate?.templateRef">
4361
+ {{ message.status }}
4362
+ </ng-container>
4363
+ </span>
4364
+ </ng-container>
4365
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
3563
4366
  <kendo-toolbar-button
3564
- *ngFor="let action of chatService.messageToolbarActions"
4367
+ *ngFor="let action of toolbarActions"
3565
4368
  fillMode="flat"
3566
4369
  [icon]="action.icon"
3567
4370
  [svgIcon]="action.svgIcon"
@@ -3571,28 +4374,7 @@ class MessageComponent extends ChatItem {
3571
4374
  >
3572
4375
  </kendo-toolbar-button>
3573
4376
  </kendo-toolbar>
3574
-
3575
- <ng-template #typing>
3576
- <div class="k-chat-bubble k-bubble">
3577
- <div class="k-typing-indicator">
3578
- <span></span>
3579
- <span></span>
3580
- <span></span>
3581
- </div>
3582
- </div>
3583
- </ng-template>
3584
-
3585
- <kendo-contextmenu
3586
- *ngIf="!message.isDeleted"
3587
- [target]="element"
3588
- [items]="contextMenuActions"
3589
- (popupOpen)="handleMenuOpen()"
3590
- (popupClose)="handleMenuClose($event)"
3591
- (select)="onContextMenuAction($event.item.originalAction)"
3592
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
3593
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
3594
- ></kendo-contextmenu>
3595
- `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
4377
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
3596
4378
  }
3597
4379
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, decorators: [{
3598
4380
  type: Component,
@@ -3605,117 +4387,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3605
4387
  }
3606
4388
  ],
3607
4389
  template: `
3608
- <time
3609
- [attr.aria-hidden]="!selected"
3610
- class="k-message-time"
3611
- *ngIf="message.timestamp"
3612
- >
3613
- {{ formatTimeStamp(message.timestamp) }}
3614
- </time>
3615
-
3616
- <ng-container *ngIf="!message.typing; else typing">
3617
- <div
3618
- class="k-chat-bubble k-bubble"
3619
- *ngIf="template"
3620
- [attr.tabindex]="0"
3621
- [ngClass]="{
3622
- 'k-selected': selected,
3623
- 'k-focus': selected,
3624
- 'k-active': active
3625
- }"
3626
- >
3627
- <div class="k-bubble-content">
3628
- <ng-container
3629
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
3630
- >
3631
- </ng-container>
3632
- </div>
3633
- </div>
4390
+ <ng-container *ngIf="useCustomBubbleTemplate">
4391
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4392
+ </ng-container>
3634
4393
 
3635
- <div
3636
- class="k-chat-bubble k-bubble"
3637
- *ngIf="!template && (message.text || message.files)"
3638
- [attr.tabindex]="0"
3639
- [ngClass]="{
3640
- 'k-bubble-expandable': chatService.allowMessageCollapse,
3641
- 'k-expanded': isMessageExpanded,
3642
- 'k-selected': selected,
3643
- 'k-focus': selected,
3644
- 'k-active': active
3645
- }"
4394
+ <ng-container *ngIf="!useCustomBubbleTemplate">
4395
+ <time
4396
+ [attr.aria-hidden]="!selected"
4397
+ class="k-message-time"
4398
+ *ngIf="chatService.timestampVisibility === 'focus' && message.timestamp"
3646
4399
  >
3647
- <div class="k-bubble-content">
3648
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
3649
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
3650
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
3651
- </div>
3652
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
3653
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
3654
- <ng-container *ngIf="!message.isDeleted && parts.length">
3655
- <ng-container *ngFor="let part of parts">
3656
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
3657
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
3658
- </ng-container>
3659
- </ng-container>
3660
- </span>
3661
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
3662
- <li
3663
- *ngFor="let file of message.files"
3664
- class="k-chat-file"
3665
- [chatFile]="file"
3666
- [fileActions]="fileActions"
3667
- (actionClick)="onFileAction($event, file)"
3668
- (actionsToggle)="onActionPopupChange($event)"
3669
- (actionButtonClick)="onActionButtonClick($event)"
3670
- ></li>
3671
- </ul>
3672
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
3673
- <button
3674
- kendoButton
3675
- class="k-chat-download-button"
3676
- fillMode="flat"
3677
- icon="download"
3678
- [svgIcon]="downloadIcon"
3679
- (click)="onDownloadAll()"
3680
- >{{ textFor('downloadAllFilesText') }}</button>
4400
+ {{ formatTimeStamp(message.timestamp) }}
4401
+ </time>
4402
+
4403
+ <ng-container *ngIf="message.typing">
4404
+ <div class="k-chat-bubble k-bubble">
4405
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
4406
+ <span></span>
4407
+ <span></span>
4408
+ <span></span>
3681
4409
  </div>
3682
4410
  </div>
3683
- <span
3684
- class="k-bubble-expandable-indicator"
3685
- *ngIf="chatService.allowMessageCollapse"
3686
- [attr.role]="'button'"
3687
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
3688
- (mousedown)="chatService.toggleMessageState = true"
3689
- (click)="toggleMessageState($event)"
4411
+ </ng-container>
4412
+
4413
+ <ng-container *ngIf="!message.typing">
4414
+ <div
4415
+ class="k-chat-bubble k-bubble"
4416
+ *ngIf="useCustomContentTemplate"
4417
+ [attr.tabindex]="0"
4418
+ [ngClass]="{
4419
+ 'k-bubble-expandable': isMessageExpandable,
4420
+ 'k-expanded': isMessageExpanded,
4421
+ 'k-selected': selected,
4422
+ 'k-focus': selected,
4423
+ 'k-active': isActiveMessage
4424
+ }"
3690
4425
  >
3691
- <kendo-icon-wrapper
3692
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
3693
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4426
+ <div class="k-bubble-content">
4427
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4428
+ </div>
4429
+ <span
4430
+ class="k-bubble-expandable-indicator"
4431
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4432
+ [attr.tabindex]="'0'"
4433
+ [attr.role]="'button'"
4434
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4435
+ (mousedown)="chatService.toggleMessageState = true"
4436
+ (click)="toggleMessageState($event)"
3694
4437
  >
3695
- </kendo-icon-wrapper>
3696
- </span>
3697
- </div>
3698
- </ng-container>
4438
+ <kendo-icon-wrapper
4439
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4440
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4441
+ >
4442
+ </kendo-icon-wrapper>
4443
+ </span>
4444
+ </div>
3699
4445
 
3700
- <span
3701
- class="k-message-status"
3702
- *ngIf="message.status"
3703
- >
3704
- <ng-container *ngIf="statusTemplate?.templateRef">
3705
- <ng-template
3706
- [ngTemplateOutlet]="statusTemplate.templateRef"
3707
- [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4446
+ <div
4447
+ class="k-chat-bubble k-bubble"
4448
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
4449
+ [attr.tabindex]="0"
4450
+ [ngClass]="{
4451
+ 'k-bubble-expandable': isMessageExpandable,
4452
+ 'k-expanded': isMessageExpanded,
4453
+ 'k-selected': selected,
4454
+ 'k-focus': selected,
4455
+ 'k-active': isActiveMessage
4456
+ }"
3708
4457
  >
3709
- </ng-template>
3710
- </ng-container>
3711
- <ng-container *ngIf="!statusTemplate?.templateRef">
3712
- {{ message.status }}
4458
+ <div class="k-bubble-content">
4459
+ <ng-container *ngIf="message.text || message.isDeleted">
4460
+ <div
4461
+ class="k-message-reference k-message-reference-receiver"
4462
+ *ngIf="message.replyToId && !message.isDeleted"
4463
+ (click)="onReplyReferenceClick($event, message.replyToId)"
4464
+ >
4465
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
4466
+ </div>
4467
+
4468
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
4469
+ {{ getDeletedMessageText() }}
4470
+ </span>
4471
+
4472
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
4473
+ <ng-container *ngFor="let part of parts">
4474
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
4475
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
4476
+ </ng-container>
4477
+ </span>
4478
+ </ng-container>
4479
+
4480
+ <ul
4481
+ class="k-chat-file-wrapper"
4482
+ *ngIf="hasFiles && !message.isDeleted"
4483
+ [ngClass]="{
4484
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
4485
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
4486
+ }"
4487
+ >
4488
+ <li
4489
+ *ngFor="let file of message.files"
4490
+ class="k-chat-file"
4491
+ [chatFile]="file"
4492
+ [fileActions]="fileActions"
4493
+ (actionClick)="onFileAction($event, file)"
4494
+ (actionsToggle)="onActionPopupChange($event)"
4495
+ (actionButtonClick)="onActionButtonClick($event)"
4496
+ ></li>
4497
+ </ul>
4498
+
4499
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
4500
+ <button
4501
+ kendoButton
4502
+ class="k-chat-download-button"
4503
+ fillMode="flat"
4504
+ icon="download"
4505
+ [svgIcon]="downloadIcon"
4506
+ [attr.title]="textFor('downloadAllFilesText')"
4507
+ (click)="onDownloadAll()"
4508
+ >{{ textFor('downloadAllFilesText') }}</button>
4509
+ </div>
4510
+ </div>
4511
+
4512
+ <span
4513
+ class="k-bubble-expandable-indicator"
4514
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4515
+ [attr.tabindex]="'0'"
4516
+ [attr.role]="'button'"
4517
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4518
+ (mousedown)="chatService.toggleMessageState = true"
4519
+ (click)="toggleMessageState($event)"
4520
+ >
4521
+ <kendo-icon-wrapper
4522
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4523
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4524
+ >
4525
+ </kendo-icon-wrapper>
4526
+ </span>
4527
+ </div>
3713
4528
  </ng-container>
3714
- </span>
3715
4529
 
3716
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
4530
+ <span class="k-message-status" *ngIf="message.status">
4531
+ <ng-container *ngIf="statusTemplate?.templateRef">
4532
+ <ng-template
4533
+ [ngTemplateOutlet]="statusTemplate.templateRef"
4534
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4535
+ >
4536
+ </ng-template>
4537
+ </ng-container>
4538
+ <ng-container *ngIf="!statusTemplate?.templateRef">
4539
+ {{ message.status }}
4540
+ </ng-container>
4541
+ </span>
4542
+ </ng-container>
4543
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
3717
4544
  <kendo-toolbar-button
3718
- *ngFor="let action of chatService.messageToolbarActions"
4545
+ *ngFor="let action of toolbarActions"
3719
4546
  fillMode="flat"
3720
4547
  [icon]="action.icon"
3721
4548
  [svgIcon]="action.svgIcon"
@@ -3725,36 +4552,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3725
4552
  >
3726
4553
  </kendo-toolbar-button>
3727
4554
  </kendo-toolbar>
3728
-
3729
- <ng-template #typing>
3730
- <div class="k-chat-bubble k-bubble">
3731
- <div class="k-typing-indicator">
3732
- <span></span>
3733
- <span></span>
3734
- <span></span>
3735
- </div>
3736
- </div>
3737
- </ng-template>
3738
-
3739
- <kendo-contextmenu
3740
- *ngIf="!message.isDeleted"
3741
- [target]="element"
3742
- [items]="contextMenuActions"
3743
- (popupOpen)="handleMenuOpen()"
3744
- (popupClose)="handleMenuClose($event)"
3745
- (select)="onContextMenuAction($event.item.originalAction)"
3746
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
3747
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
3748
- ></kendo-contextmenu>
3749
4555
  `,
3750
4556
  standalone: true,
3751
- imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ContextMenuComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
4557
+ imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
3752
4558
  }]
3753
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }], propDecorators: { message: [{
4559
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { message: [{
3754
4560
  type: Input
3755
4561
  }], tabbable: [{
3756
4562
  type: Input
3757
- }], template: [{
4563
+ }], authorMessageContentTemplate: [{
4564
+ type: Input
4565
+ }], receiverMessageContentTemplate: [{
4566
+ type: Input
4567
+ }], messageContentTemplate: [{
4568
+ type: Input
4569
+ }], authorMessageTemplate: [{
4570
+ type: Input
4571
+ }], receiverMessageTemplate: [{
4572
+ type: Input
4573
+ }], messageTemplate: [{
3758
4574
  type: Input
3759
4575
  }], statusTemplate: [{
3760
4576
  type: Input
@@ -3762,8 +4578,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3762
4578
  type: Input
3763
4579
  }], authorId: [{
3764
4580
  type: Input
3765
- }], contextMenuVisibilityChange: [{
3766
- type: Output
3767
4581
  }], cssClass: [{
3768
4582
  type: HostBinding,
3769
4583
  args: ['class.k-message']
@@ -3776,9 +4590,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3776
4590
  }], tabIndex: [{
3777
4591
  type: HostBinding,
3778
4592
  args: ['attr.tabIndex']
3779
- }], onContextMenu: [{
3780
- type: HostListener,
3781
- args: ['contextmenu', ['$event']]
3782
4593
  }] } });
3783
4594
 
3784
4595
  /**
@@ -3894,10 +4705,10 @@ class MessageAttachmentsComponent extends ChatItem {
3894
4705
  scrollSubscription;
3895
4706
  direction;
3896
4707
  get showLeftArrow() {
3897
- return this.carousel && this.direction === 'rtl' ? this.scrollPosition > -1 : this.scrollPosition > 0;
4708
+ return this.carousel && (this.direction === 'rtl' ? this.scrollPosition > -1 : this.scrollPosition > 0);
3898
4709
  }
3899
4710
  get showRightArrow() {
3900
- return this.carousel && this.direction === 'rtl' ? this.scrollPosition < 0 : this.scrollPosition < 1;
4711
+ return this.carousel && (this.direction === 'rtl' ? this.scrollPosition < 0 : this.scrollPosition < 1);
3901
4712
  }
3902
4713
  carouselKeyHandlers = {
3903
4714
  [Keys.ArrowLeft]: (e) => this.navigateTo(e, this.direction === 'rtl' ? 1 : -1),
@@ -4100,6 +4911,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4100
4911
  args: ['item', { read: ElementRef }]
4101
4912
  }] } });
4102
4913
 
4914
+ /**
4915
+ * Defines a template for displaying user status in the Chat.
4916
+ *
4917
+ * To define a user status template, nest an `<ng-template>` tag with the `kendoChatUserStatusTemplate` directive inside the `<kendo-chat>` component.
4918
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
4919
+ *
4920
+ * @example
4921
+ * ```html
4922
+ * <kendo-chat>
4923
+ * <ng-template kendoChatUserStatusTemplate let-status>
4924
+ * <div>{{ status }}</div>
4925
+ * </ng-template>
4926
+ * </kendo-chat>
4927
+ * ```
4928
+ */
4929
+ class ChatUserStatusTemplateDirective {
4930
+ templateRef;
4931
+ constructor(templateRef) {
4932
+ this.templateRef = templateRef;
4933
+ }
4934
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatUserStatusTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
4935
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ChatUserStatusTemplateDirective, isStandalone: true, selector: "[kendoChatUserStatusTemplate]", ngImport: i0 });
4936
+ }
4937
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatUserStatusTemplateDirective, decorators: [{
4938
+ type: Directive,
4939
+ args: [{
4940
+ selector: '[kendoChatUserStatusTemplate]',
4941
+ standalone: true
4942
+ }]
4943
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
4944
+ type: Optional
4945
+ }] }] });
4946
+
4103
4947
  /* eslint-disable @typescript-eslint/no-unused-vars */
4104
4948
  /* eslint-disable @typescript-eslint/no-explicit-any */
4105
4949
  /**
@@ -4120,9 +4964,15 @@ class MessageListComponent {
4120
4964
  return this._messages;
4121
4965
  }
4122
4966
  attachmentTemplate;
4967
+ authorMessageContentTemplate;
4968
+ receiverMessageContentTemplate;
4969
+ messageContentTemplate;
4970
+ authorMessageTemplate;
4971
+ receiverMessageTemplate;
4123
4972
  messageTemplate;
4124
4973
  timestampTemplate;
4125
4974
  statusTemplate;
4975
+ userStatusTemplate;
4126
4976
  localization;
4127
4977
  authorId;
4128
4978
  executeAction = new EventEmitter();
@@ -4140,6 +4990,7 @@ class MessageListComponent {
4140
4990
  [Keys.ArrowUp]: (e) => this.navigateTo(e, -1),
4141
4991
  [Keys.ArrowDown]: (e) => this.navigateTo(e, 1),
4142
4992
  [Keys.Tab]: (e) => this.onTabKeyDown(e),
4993
+ [Keys.F10]: (e) => e.shiftKey && this.openContextMenu(e),
4143
4994
  };
4144
4995
  constructor(element, intl, renderer, chatService, cdr) {
4145
4996
  this.element = element;
@@ -4152,9 +5003,25 @@ class MessageListComponent {
4152
5003
  const elRef = this.element.nativeElement;
4153
5004
  this.subs.add(this.renderer.listen(elRef, 'keydown', event => this.onKeydown(event)));
4154
5005
  this.subs.add(this.renderer.listen(elRef, 'focusout', event => this.onBlur(event)));
5006
+ this.subs.add(this.renderer.listen(elRef, 'click', event => {
5007
+ const messageComponent = this.findMessageComponentFromEvent(event);
5008
+ if (messageComponent) {
5009
+ this.select(messageComponent, event);
5010
+ }
5011
+ }));
5012
+ this.subs.add(this.renderer.listen(elRef, 'contextmenu', event => {
5013
+ event.preventDefault();
5014
+ const messageComponent = this.findMessageComponentFromEvent(event);
5015
+ if (messageComponent) {
5016
+ this.onContextMenuClick(messageComponent.message, event, messageComponent);
5017
+ }
5018
+ }));
4155
5019
  this.subs.add(this.chatService.replyReferenceClick$.subscribe((messageId) => {
4156
5020
  this.scrollToAndSelectMessage(messageId);
4157
5021
  }));
5022
+ this.subs.add(this.chatService.contextMenuVisibilityChange$.subscribe((isVisible) => {
5023
+ this.handleMenuClose(isVisible);
5024
+ }));
4158
5025
  }
4159
5026
  ngAfterViewInit() {
4160
5027
  this.selectedItem = this.items.last;
@@ -4168,9 +5035,45 @@ class MessageListComponent {
4168
5035
  onClick(message, event) {
4169
5036
  this.select(message, event);
4170
5037
  }
5038
+ showGroupAuthor(group) {
5039
+ const messageSettings = this.isOwnMessage(group.messages[0]) ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings;
5040
+ if (isPresent(messageSettings?.showUsername)) {
5041
+ return messageSettings.showUsername && group.author.name;
5042
+ }
5043
+ return this.chatService.showUsername && group.author.name;
5044
+ }
5045
+ showGroupAvatar(group) {
5046
+ const messageSettings = this.isOwnMessage(group.messages[0]) ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings;
5047
+ if (isPresent(messageSettings?.showAvatar)) {
5048
+ return messageSettings.showAvatar && group.author.avatarUrl;
5049
+ }
5050
+ return this.chatService.showAvatar && group.author.avatarUrl;
5051
+ }
5052
+ onContextMenuClick(message, event, messageElement) {
5053
+ this.chatService.calculateContextMenuActions(this.isOwnMessage(message));
5054
+ if (this.chatService.calculatedContextMenuActions.length > 0) {
5055
+ this.chatService.messagesContextMenu.show({
5056
+ left: event.pageX,
5057
+ top: event.pageY,
5058
+ });
5059
+ this.chatService.activeMessageElement = messageElement;
5060
+ this.chatService.activeMessage = message;
5061
+ this.chatService.active = true;
5062
+ this.chatService.selectOnMenuClose = this.chatService.activeMessageElement?.selected;
5063
+ this.chatService.emit('contextMenuVisibilityChange', true);
5064
+ }
5065
+ }
4171
5066
  formatTimeStamp(date) {
4172
5067
  return this.intl.formatDate(date, { date: 'full' });
4173
5068
  }
5069
+ calculateMessageWidthMode(message) {
5070
+ const isOwn = this.isOwnMessage(message);
5071
+ const messageSettings = isOwn ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings;
5072
+ if (messageSettings?.messageWidthMode) {
5073
+ return messageSettings.messageWidthMode === 'full';
5074
+ }
5075
+ return this.chatService.messageWidthMode === 'full';
5076
+ }
4174
5077
  onKeydown(e) {
4175
5078
  // On some keyboards Numpad keys are used for Home/End/PageUp/PageDown.
4176
5079
  const code = normalizeNumpadKeys(e);
@@ -4230,6 +5133,9 @@ class MessageListComponent {
4230
5133
  this.select(null);
4231
5134
  }
4232
5135
  }
5136
+ textFor(key) {
5137
+ return this.localization.get(key);
5138
+ }
4233
5139
  onHomeOrEndKeyDown(key) {
4234
5140
  const items = this.items.toArray();
4235
5141
  if (key === 'home') {
@@ -4276,11 +5182,49 @@ class MessageListComponent {
4276
5182
  this.select(chatItem);
4277
5183
  }
4278
5184
  }
4279
- textFor(key) {
4280
- return this.localization.get(key);
5185
+ openContextMenu(event) {
5186
+ event.preventDefault();
5187
+ const messageComponent = this.findMessageComponentFromActiveElement();
5188
+ if (messageComponent) {
5189
+ const messageContentElement = messageComponent.element.nativeElement.querySelector('.k-chat-bubble');
5190
+ if (messageContentElement) {
5191
+ const rect = messageContentElement?.getBoundingClientRect();
5192
+ this.onContextMenuClick(messageComponent.message, {
5193
+ pageX: rect.left + (rect.width / 2) + window.scrollX,
5194
+ pageY: rect.top + (rect.height / 2) + window.scrollY
5195
+ }, messageComponent);
5196
+ }
5197
+ else {
5198
+ const messageElement = messageComponent.element.nativeElement;
5199
+ const rect = messageElement?.getBoundingClientRect();
5200
+ this.onContextMenuClick(messageComponent.message, {
5201
+ pageX: rect.left + (rect.width / 2) + window.scrollX,
5202
+ pageY: rect.top + (rect.height / 2) + window.scrollY
5203
+ }, messageComponent);
5204
+ }
5205
+ }
5206
+ }
5207
+ findMessageComponentFromActiveElement() {
5208
+ const activeElement = document.activeElement;
5209
+ const messageComponents = this.items.filter(item => item instanceof MessageComponent);
5210
+ return messageComponents.find(component => {
5211
+ const componentElement = component.element?.nativeElement;
5212
+ return componentElement && (componentElement === activeElement || componentElement.contains(activeElement));
5213
+ });
5214
+ }
5215
+ findMessageComponentFromEvent(event) {
5216
+ const target = event.target;
5217
+ const clickedElement = target?.closest('.k-message');
5218
+ if (!clickedElement)
5219
+ return undefined;
5220
+ const messageComponents = this.items.filter(item => item instanceof MessageComponent);
5221
+ return messageComponents.find(component => {
5222
+ const componentElement = component.element?.nativeElement;
5223
+ return componentElement && (componentElement === clickedElement || componentElement.contains(clickedElement));
5224
+ });
4281
5225
  }
4282
5226
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageListComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.IntlService }, { token: i0.Renderer2 }, { token: ChatService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4283
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageListComponent, isStandalone: true, selector: "kendo-chat-message-list", inputs: { messages: "messages", attachmentTemplate: "attachmentTemplate", messageTemplate: "messageTemplate", timestampTemplate: "timestampTemplate", statusTemplate: "statusTemplate", localization: "localization", authorId: "authorId" }, outputs: { executeAction: "executeAction", navigate: "navigate", resize: "resize" }, host: { properties: { "class.k-message-list-content": "this.cssClass" } }, viewQueries: [{ propertyName: "items", predicate: ChatItem, descendants: true }], ngImport: i0, template: `
5227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageListComponent, isStandalone: true, selector: "kendo-chat-message-list", inputs: { messages: "messages", attachmentTemplate: "attachmentTemplate", authorMessageContentTemplate: "authorMessageContentTemplate", receiverMessageContentTemplate: "receiverMessageContentTemplate", messageContentTemplate: "messageContentTemplate", authorMessageTemplate: "authorMessageTemplate", receiverMessageTemplate: "receiverMessageTemplate", messageTemplate: "messageTemplate", timestampTemplate: "timestampTemplate", statusTemplate: "statusTemplate", userStatusTemplate: "userStatusTemplate", localization: "localization", authorId: "authorId" }, outputs: { executeAction: "executeAction", navigate: "navigate", resize: "resize" }, host: { properties: { "class.k-message-list-content": "this.cssClass" } }, viewQueries: [{ propertyName: "items", predicate: ChatItem, descendants: true }], ngImport: i0, template: `
4284
5228
  <ng-container *ngFor="let group of view; last as lastGroup; trackBy: trackGroup">
4285
5229
  <ng-container [ngSwitch]="group.type">
4286
5230
  <div
@@ -4300,11 +5244,11 @@ class MessageListComponent {
4300
5244
  class="k-message-group"
4301
5245
  [class.k-message-group-sender]="isOwnMessage(group.messages[0])"
4302
5246
  [class.k-message-group-receiver]="!isOwnMessage(group.messages[0])"
4303
- [class.k-no-avatar]="!group.author.avatarUrl"
4304
- [class.k-message-group-full-width]="chatService.messageWidthMode === 'full'"
5247
+ [class.k-no-avatar]="!showGroupAvatar(group)"
5248
+ [class.k-message-group-full-width]="calculateMessageWidthMode(group.messages[0])"
4305
5249
  >
4306
5250
  <div
4307
- *ngIf="group.author.avatarUrl"
5251
+ *ngIf="!userStatusTemplate?.templateRef && showGroupAvatar(group)"
4308
5252
  class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4309
5253
  >
4310
5254
  <span class="k-avatar-image">
@@ -4314,29 +5258,41 @@ class MessageListComponent {
4314
5258
  />
4315
5259
  </span>
4316
5260
  </div>
5261
+ <div class="k-chat-user-status-wrapper" *ngIf="userStatusTemplate?.templateRef && showGroupAvatar(group)">
5262
+ <div
5263
+ class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
5264
+ >
5265
+ <span class="k-avatar-image">
5266
+ <img
5267
+ [attr.src]="group.author.avatarUrl"
5268
+ [alt]="group.author.avatarAltText"
5269
+ />
5270
+ </span>
5271
+ </div>
5272
+ <div class="k-chat-user-status" *ngIf="userStatusTemplate?.templateRef">
5273
+ <ng-template
5274
+ [ngTemplateOutlet]="userStatusTemplate.templateRef"
5275
+ [ngTemplateOutletContext]="{ $implicit: group.messages.at(-1) }"
5276
+ >
5277
+ </ng-template>
5278
+ </div>
5279
+ </div>
4317
5280
  <div class="k-message-group-content">
4318
- <p *ngIf="group.author.name" class="k-message-author">{{ group.author.name }}</p>
5281
+ <p *ngIf="showGroupAuthor(group)" class="k-message-author">{{ group.author.name }}</p>
4319
5282
  <ng-container
4320
5283
  *ngFor="let msg of group.messages; first as firstMessage; last as lastMessage"
4321
5284
  >
4322
- <div
4323
- *ngIf="msg.user?.avatarUrl"
4324
- class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4325
- >
4326
- <span class="k-avatar-image">
4327
- <img [src]="msg.user?.avatarUrl">
4328
- </span>
4329
- </div>
4330
5285
  <kendo-chat-message #message
4331
5286
  [message]="msg"
4332
5287
  [tabbable]="lastGroup && lastMessage"
4333
- [template]="messageTemplate"
5288
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
5289
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
5290
+ [messageContentTemplate]="messageContentTemplate"
5291
+ [authorMessageTemplate]="authorMessageTemplate"
5292
+ [receiverMessageTemplate]="receiverMessageTemplate"
5293
+ [messageTemplate]="messageTemplate"
4334
5294
  [statusTemplate]="statusTemplate"
4335
5295
  [authorId]="authorId"
4336
- (click)="onClick(message, $event)"
4337
- (contextMenuVisibilityChange)="handleMenuClose($event)"
4338
- [class.k-first]="group.messages.length > 1 && firstMessage"
4339
- [class.k-last]="group.messages.length > 1 && lastMessage"
4340
5296
  >
4341
5297
  </kendo-chat-message>
4342
5298
 
@@ -4365,6 +5321,7 @@ class MessageListComponent {
4365
5321
  <kendo-chat-suggested-actions #actions
4366
5322
  *ngSwitchCase="'action-group'"
4367
5323
  [actions]="group.actions"
5324
+ type="action"
4368
5325
  [tabbable]="lastGroup"
4369
5326
  (dispatchAction)="dispatchAction($event, last(view))"
4370
5327
  (click)="select(actions, $event)"
@@ -4375,7 +5332,7 @@ class MessageListComponent {
4375
5332
  </ng-container>
4376
5333
  <kendo-resize-sensor (resize)="onResize()">
4377
5334
  </kendo-resize-sensor>
4378
- `, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MessageComponent, selector: "kendo-chat-message", inputs: ["message", "tabbable", "template", "statusTemplate", "showMessageTime", "authorId"], outputs: ["contextMenuVisibilityChange"] }, { kind: "component", type: AttachmentComponent, selector: "kendo-chat-attachment", inputs: ["attachment", "template"] }, { kind: "component", type: MessageAttachmentsComponent, selector: "kendo-chat-message-attachments", inputs: ["attachments", "layout", "tabbable", "template", "localization"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }] });
5335
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MessageComponent, selector: "kendo-chat-message", inputs: ["message", "tabbable", "authorMessageContentTemplate", "receiverMessageContentTemplate", "messageContentTemplate", "authorMessageTemplate", "receiverMessageTemplate", "messageTemplate", "statusTemplate", "showMessageTime", "authorId"] }, { kind: "component", type: AttachmentComponent, selector: "kendo-chat-attachment", inputs: ["attachment", "template"] }, { kind: "component", type: MessageAttachmentsComponent, selector: "kendo-chat-message-attachments", inputs: ["attachments", "layout", "tabbable", "template", "localization"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "type", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }] });
4379
5336
  }
4380
5337
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageListComponent, decorators: [{
4381
5338
  type: Component,
@@ -4401,11 +5358,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4401
5358
  class="k-message-group"
4402
5359
  [class.k-message-group-sender]="isOwnMessage(group.messages[0])"
4403
5360
  [class.k-message-group-receiver]="!isOwnMessage(group.messages[0])"
4404
- [class.k-no-avatar]="!group.author.avatarUrl"
4405
- [class.k-message-group-full-width]="chatService.messageWidthMode === 'full'"
5361
+ [class.k-no-avatar]="!showGroupAvatar(group)"
5362
+ [class.k-message-group-full-width]="calculateMessageWidthMode(group.messages[0])"
4406
5363
  >
4407
5364
  <div
4408
- *ngIf="group.author.avatarUrl"
5365
+ *ngIf="!userStatusTemplate?.templateRef && showGroupAvatar(group)"
4409
5366
  class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4410
5367
  >
4411
5368
  <span class="k-avatar-image">
@@ -4415,29 +5372,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4415
5372
  />
4416
5373
  </span>
4417
5374
  </div>
5375
+ <div class="k-chat-user-status-wrapper" *ngIf="userStatusTemplate?.templateRef && showGroupAvatar(group)">
5376
+ <div
5377
+ class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
5378
+ >
5379
+ <span class="k-avatar-image">
5380
+ <img
5381
+ [attr.src]="group.author.avatarUrl"
5382
+ [alt]="group.author.avatarAltText"
5383
+ />
5384
+ </span>
5385
+ </div>
5386
+ <div class="k-chat-user-status" *ngIf="userStatusTemplate?.templateRef">
5387
+ <ng-template
5388
+ [ngTemplateOutlet]="userStatusTemplate.templateRef"
5389
+ [ngTemplateOutletContext]="{ $implicit: group.messages.at(-1) }"
5390
+ >
5391
+ </ng-template>
5392
+ </div>
5393
+ </div>
4418
5394
  <div class="k-message-group-content">
4419
- <p *ngIf="group.author.name" class="k-message-author">{{ group.author.name }}</p>
5395
+ <p *ngIf="showGroupAuthor(group)" class="k-message-author">{{ group.author.name }}</p>
4420
5396
  <ng-container
4421
5397
  *ngFor="let msg of group.messages; first as firstMessage; last as lastMessage"
4422
5398
  >
4423
- <div
4424
- *ngIf="msg.user?.avatarUrl"
4425
- class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4426
- >
4427
- <span class="k-avatar-image">
4428
- <img [src]="msg.user?.avatarUrl">
4429
- </span>
4430
- </div>
4431
5399
  <kendo-chat-message #message
4432
5400
  [message]="msg"
4433
5401
  [tabbable]="lastGroup && lastMessage"
4434
- [template]="messageTemplate"
5402
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
5403
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
5404
+ [messageContentTemplate]="messageContentTemplate"
5405
+ [authorMessageTemplate]="authorMessageTemplate"
5406
+ [receiverMessageTemplate]="receiverMessageTemplate"
5407
+ [messageTemplate]="messageTemplate"
4435
5408
  [statusTemplate]="statusTemplate"
4436
5409
  [authorId]="authorId"
4437
- (click)="onClick(message, $event)"
4438
- (contextMenuVisibilityChange)="handleMenuClose($event)"
4439
- [class.k-first]="group.messages.length > 1 && firstMessage"
4440
- [class.k-last]="group.messages.length > 1 && lastMessage"
4441
5410
  >
4442
5411
  </kendo-chat-message>
4443
5412
 
@@ -4466,6 +5435,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4466
5435
  <kendo-chat-suggested-actions #actions
4467
5436
  *ngSwitchCase="'action-group'"
4468
5437
  [actions]="group.actions"
5438
+ type="action"
4469
5439
  [tabbable]="lastGroup"
4470
5440
  (dispatchAction)="dispatchAction($event, last(view))"
4471
5441
  (click)="select(actions, $event)"
@@ -4484,12 +5454,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4484
5454
  type: Input
4485
5455
  }], attachmentTemplate: [{
4486
5456
  type: Input
5457
+ }], authorMessageContentTemplate: [{
5458
+ type: Input
5459
+ }], receiverMessageContentTemplate: [{
5460
+ type: Input
5461
+ }], messageContentTemplate: [{
5462
+ type: Input
5463
+ }], authorMessageTemplate: [{
5464
+ type: Input
5465
+ }], receiverMessageTemplate: [{
5466
+ type: Input
4487
5467
  }], messageTemplate: [{
4488
5468
  type: Input
4489
5469
  }], timestampTemplate: [{
4490
5470
  type: Input
4491
5471
  }], statusTemplate: [{
4492
5472
  type: Input
5473
+ }], userStatusTemplate: [{
5474
+ type: Input
4493
5475
  }], localization: [{
4494
5476
  type: Input
4495
5477
  }], authorId: [{
@@ -4652,8 +5634,16 @@ let Messages$1 = class Messages extends ComponentMessages {
4652
5634
  * Sets the title of the DropDownButton that opens the File actions.
4653
5635
  */
4654
5636
  fileActionsTitle;
5637
+ /**
5638
+ * Sets the title of the button that shows the **Scroll right** when the suggestions list is scrollable with buttons.
5639
+ */
5640
+ nextSuggestionsButtonTitle;
5641
+ /**
5642
+ * Sets the title of the button that shows the **Scroll left** when the suggestions list is scrollable with buttons.
5643
+ */
5644
+ previousSuggestionsButtonTitle;
4655
5645
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: Messages, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4656
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: Messages, selector: "kendoConversationalUIMessages", inputs: { deletedMessageSenderText: "deletedMessageSenderText", deletedMessageReceiverText: "deletedMessageReceiverText", downloadAllFilesText: "downloadAllFilesText", messagePlaceholder: "messagePlaceholder", send: "send", messageListLabel: "messageListLabel", messageBoxInputLabel: "messageBoxInputLabel", messageAttachmentLeftArrow: "messageAttachmentLeftArrow", messageAttachmentRightArrow: "messageAttachmentRightArrow", speechToTextButtonTitle: "speechToTextButtonTitle", fileSelectButtonTitle: "fileSelectButtonTitle", removeReplyTitle: "removeReplyTitle", removeFileTitle: "removeFileTitle", expandTitle: "expandTitle", collapseTitle: "collapseTitle", fileActionsTitle: "fileActionsTitle" }, usesInheritance: true, ngImport: i0 });
5646
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: Messages, selector: "kendoConversationalUIMessages", inputs: { deletedMessageSenderText: "deletedMessageSenderText", deletedMessageReceiverText: "deletedMessageReceiverText", downloadAllFilesText: "downloadAllFilesText", messagePlaceholder: "messagePlaceholder", send: "send", messageListLabel: "messageListLabel", messageBoxInputLabel: "messageBoxInputLabel", messageAttachmentLeftArrow: "messageAttachmentLeftArrow", messageAttachmentRightArrow: "messageAttachmentRightArrow", speechToTextButtonTitle: "speechToTextButtonTitle", fileSelectButtonTitle: "fileSelectButtonTitle", removeReplyTitle: "removeReplyTitle", removeFileTitle: "removeFileTitle", expandTitle: "expandTitle", collapseTitle: "collapseTitle", fileActionsTitle: "fileActionsTitle", nextSuggestionsButtonTitle: "nextSuggestionsButtonTitle", previousSuggestionsButtonTitle: "previousSuggestionsButtonTitle" }, usesInheritance: true, ngImport: i0 });
4657
5647
  };
4658
5648
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: Messages$1, decorators: [{
4659
5649
  type: Directive,
@@ -4693,6 +5683,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4693
5683
  type: Input
4694
5684
  }], fileActionsTitle: [{
4695
5685
  type: Input
5686
+ }], nextSuggestionsButtonTitle: [{
5687
+ type: Input
5688
+ }], previousSuggestionsButtonTitle: [{
5689
+ type: Input
4696
5690
  }] } });
4697
5691
 
4698
5692
  // eslint-disable no-forward-ref
@@ -4755,6 +5749,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4755
5749
  }]
4756
5750
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
4757
5751
 
5752
+ /**
5753
+ * Defines a template for displaying custom content when there are no messages in the Chat.
5754
+ *
5755
+ * To define an empty Chat template, nest an `<ng-template>` tag with the `kendoChatNoDataTemplate` directive inside the `<kendo-chat>` component.
5756
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
5757
+ *
5758
+ * @example
5759
+ * ```html
5760
+ * <kendo-chat>
5761
+ * <ng-template kendoChatNoDataTemplate>
5762
+ * <div>No messages.</div>
5763
+ * </ng-template>
5764
+ * </kendo-chat>
5765
+ * ```
5766
+ */
5767
+ class NoDataTemplateDirective {
5768
+ templateRef;
5769
+ constructor(templateRef) {
5770
+ this.templateRef = templateRef;
5771
+ }
5772
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NoDataTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
5773
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: NoDataTemplateDirective, isStandalone: true, selector: "[kendoChatNoDataTemplate]", ngImport: i0 });
5774
+ }
5775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NoDataTemplateDirective, decorators: [{
5776
+ type: Directive,
5777
+ args: [{
5778
+ selector: '[kendoChatNoDataTemplate]',
5779
+ standalone: true
5780
+ }]
5781
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
5782
+ type: Optional
5783
+ }] }] });
5784
+
4758
5785
  /**
4759
5786
  * Represents the [Kendo UI Chat component for Angular](slug:overview_convui).
4760
5787
  *
@@ -4772,7 +5799,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4772
5799
  * ```
4773
5800
  *
4774
5801
  * @remarks
4775
- * Supported children components are: {@link CustomMessagesComponent}, {@link HeroCardComponent}, {@link AttachmentTemplateDirective}, {@link ChatHeaderTemplateDirective}, {@link ChatMessageBoxTemplateDirective}, {@link MessageTemplateDirective}, {@link ChatStatusTemplateDirective}, {@link ChatSuggestionTemplateDirective}, {@link ChatTimestampTemplateDirective}.
5802
+ * Supported children components are: {@link CustomMessagesComponent}, {@link HeroCardComponent}, {@link AttachmentTemplateDirective}, {@link ChatHeaderTemplateDirective}, {@link ChatMessageBoxTemplateDirective}, {@link MessageContentTemplateDirective}, {@link MessageTemplateDirective},{@link ChatStatusTemplateDirective}, {@link ChatSuggestionTemplateDirective}, {@link ChatTimestampTemplateDirective}, {@link ChatNoDataTemplateDirective}, {@link ChatUserStatusTemplateDirective}, {@link AuthorMessageContentTemplateDirective}, {@link ReceiverMessageContentTemplateDirective}, {@link ReceiverMessageTemplateDirective}, {@link AuthorMessageTemplateDirective}.
4776
5803
  */
4777
5804
  class ChatComponent {
4778
5805
  localization;
@@ -4781,13 +5808,13 @@ class ChatComponent {
4781
5808
  element;
4782
5809
  chatService;
4783
5810
  /**
4784
- * Defines the array of messages displayed in the Chat.
4785
- * Each message can include a timestamp for unique identification.
4786
- * For more information, see [ngFor - Change Tracking](link:site.data.urls.angular['ngforof']#change-propagation).
5811
+ * Sets the Chat messages.
5812
+ * Accepts an array of `Message` objects, but can also accept custom data types.
5813
+ * For more information, check [Data Binding](slug:databinding_chat) section in the documentation.
4787
5814
  */
4788
5815
  messages;
4789
5816
  /**
4790
- * Specifies the id representing the local user.
5817
+ * Sets the ID that represents the local user.
4791
5818
  */
4792
5819
  authorId;
4793
5820
  /**
@@ -4815,13 +5842,34 @@ class ChatComponent {
4815
5842
  */
4816
5843
  placeholder;
4817
5844
  /**
4818
- * Switches the width of the message between the predefined options.
5845
+ * Controls the width of the message between the predefined options.
4819
5846
  *
4820
5847
  * @default 'standard'
4821
5848
  */
4822
5849
  messageWidthMode = 'standard';
5850
+ /**
5851
+ * Controls the visibility of timestamps in messages.
5852
+ *
5853
+ * @default 'focus'
5854
+ */
5855
+ timestampVisibility = 'focus';
5856
+ /**
5857
+ * Controls the visibility of usernames in messages.
5858
+ * When set to `true`, the username displays above each message bubble.
5859
+ *
5860
+ * @default true
5861
+ */
5862
+ showUsername = true;
5863
+ /**
5864
+ * Controls the avatar visibility for the messages.
5865
+ * When set to `true`, the user avatar displays next to each message bubble.
5866
+ *
5867
+ * @default true
5868
+ */
5869
+ showAvatar = true;
4823
5870
  /**
4824
5871
  * Enables the expand or collapse functionality for messages.
5872
+ *
4825
5873
  * @default false
4826
5874
  */
4827
5875
  allowMessageCollapse = false;
@@ -4838,24 +5886,34 @@ class ChatComponent {
4838
5886
  */
4839
5887
  enableFileSelect = true;
4840
5888
  /**
4841
- * Specifies the actions available in the message toolbar.
5889
+ * Sets the actions of the message toolbar.
4842
5890
  * These actions display in the message toolbar and let you perform specific operations on the message.
5891
+ *
4843
5892
  * @default []
4844
5893
  */
4845
5894
  messageToolbarActions = [];
4846
5895
  /**
4847
5896
  * Sets the value of the Message Box.
5897
+ *
4848
5898
  * @default ''
4849
5899
  */
4850
5900
  inputValue = '';
4851
5901
  /**
4852
- * Specifies the default actions available in the message context menu.
5902
+ * Sets the settings for the author's messages.
5903
+ */
5904
+ authorMessageSettings;
5905
+ /**
5906
+ * Sets the settings for the receivers' messages.
5907
+ */
5908
+ receiverMessageSettings;
5909
+ /**
5910
+ * Sets the default actions that display in the message context menu.
4853
5911
  *
4854
5912
  * @default [{ id: 'copy', label: 'Copy', icon: 'copy', svgIcon: copyIcon, disabled: false }, { id: 'reply', label: 'Reply', icon: 'undo', svgIcon: undoIcon, disabled: false }]
4855
5913
  */
4856
5914
  defaultContextMenuActions = CONTEXT_MENU_ACTIONS;
4857
5915
  /**
4858
- * Specifies the actions available in the message as a context menu.
5916
+ * Sets the actions that display in the message as a context menu.
4859
5917
  * These actions display as menu items and let you perform specific operations on the message.
4860
5918
  * The default actions are `copy` and `reply` and are defined by their `id`.
4861
5919
  */
@@ -4866,11 +5924,13 @@ class ChatComponent {
4866
5924
  return this._messageContextMenuActions;
4867
5925
  }
4868
5926
  /**
4869
- * Specifies the default actions available in the file actions DropDownButton.
5927
+ * Sets the default actions that display in the file actions DropDownButton.
5928
+ *
5929
+ * @default [{ id: 'download', label: 'Download', icon: 'download', svgIcon: downloadIcon, disabled: false }]
4870
5930
  */
4871
5931
  defaultFileActions = FILE_ACTIONS;
4872
5932
  /**
4873
- * Specifies the actions available in the file as items of a DropDownButton.
5933
+ * Sets the actions that display in the file as items of a DropDownButton.
4874
5934
  * These actions display when you click the file DropDownButton and let you perform specific operations on the file.
4875
5935
  * The default action is `download` and is defined by its `id`.
4876
5936
  *
@@ -4883,17 +5943,40 @@ class ChatComponent {
4883
5943
  return this._fileActions;
4884
5944
  }
4885
5945
  /**
4886
- * Sets the suggestions to display in the message input box.
4887
- * Suggestions display as a list of clickable items that let you quickly insert predefined text into the message input.
4888
- * @default []
5946
+ * Sets the layout of the files in the message bubble.
5947
+ *
5948
+ * @default 'vertical'
4889
5949
  */
4890
- suggestions = [];
5950
+ set messageFilesLayout(layout) {
5951
+ this.chatService.messageFilesLayout = layout;
5952
+ }
4891
5953
  /**
4892
- * Sets the visibility of the message toolbar.
5954
+ * Sets the layout of the suggestions above the message input box.
4893
5955
  *
4894
- * @default 'hidden'
5956
+ * @default 'scroll'
4895
5957
  */
4896
- messageToolbarVisibility = 'hidden';
5958
+ set suggestionsLayout(layoutMode) {
5959
+ if (layoutMode) {
5960
+ this.chatService.suggestionsLayout = layoutMode;
5961
+ }
5962
+ }
5963
+ /**
5964
+ * Sets the layout of the quick actions suggested below the messages.
5965
+ *
5966
+ * @default 'scroll'
5967
+ */
5968
+ set quickActionsLayout(layoutMode) {
5969
+ if (layoutMode) {
5970
+ this.chatService.quickActionsLayout = layoutMode;
5971
+ }
5972
+ }
5973
+ /**
5974
+ * Sets the suggestions that display in the message input box.
5975
+ * Suggestions display as a list of clickable items that let you quickly insert predefined text into the message input.
5976
+ *
5977
+ * @default []
5978
+ */
5979
+ suggestions = [];
4897
5980
  /**
4898
5981
  * Sets the send button settings for the Chat component.
4899
5982
  * Allows customization of the send button appearance, icons and disabled state.
@@ -4903,7 +5986,7 @@ class ChatComponent {
4903
5986
  sendButtonSettings = SEND_BTN_DEFAULT_SETTINGS;
4904
5987
  /**
4905
5988
  * Sets the names of the model fields from which the Chat reads its data.
4906
- * Lets you map custom data structures to the expected `Message` format.
5989
+ * Lets you map custom data types to the expected `Message` format.
4907
5990
  */
4908
5991
  set modelFields(value) {
4909
5992
  this._modelFields = { ...defaultModelFields, ...value };
@@ -4912,53 +5995,53 @@ class ChatComponent {
4912
5995
  return this._modelFields;
4913
5996
  }
4914
5997
  /**
4915
- * Emits when the user sends a message by clicking the **Send** button or pressing **Enter**.
5998
+ * Fires when the user sends a message by clicking the **Send** button or pressing **Enter**.
4916
5999
  *
4917
6000
  * The message is not automatically added to the `messages` array.
4918
6001
  */
4919
6002
  sendMessage = new EventEmitter();
4920
6003
  /**
4921
- * Emits when the user clicks a quick action button in the message toolbar.
6004
+ * Fires when the user clicks a quick action button in the message toolbar.
4922
6005
  */
4923
6006
  toolbarActionClick = new EventEmitter();
4924
6007
  /**
4925
- * Emits when the user clicks an action in the message context menu.
6008
+ * Fires when the user clicks an action in the message context menu.
4926
6009
  */
4927
6010
  contextMenuActionClick = new EventEmitter();
4928
6011
  /**
4929
- * Emits when the user clicks an action in the file context menu.
6012
+ * Fires when the user clicks an action in the file context menu.
4930
6013
  */
4931
6014
  fileActionClick = new EventEmitter();
4932
6015
  /**
4933
- * Emits when the user clicks an action in the file context menu.
6016
+ * Fires when the user clicks an action in the file context menu.
4934
6017
  */
4935
6018
  download = new EventEmitter();
4936
6019
  /**
4937
- * Emits when the user clicks a quick action button.
6020
+ * Fires when the user clicks a quick action button.
4938
6021
  * The Chat internally handles [known actions](slug:api_conversational-ui_actiontype) such as `reply`, `openUrl`, and `call`.
4939
6022
  *
4940
6023
  * The event is preventable. Calling [`preventDefault`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) suppresses the built-in action.
4941
6024
  */
4942
6025
  executeAction = new EventEmitter();
4943
6026
  /**
4944
- * Emits when the user clicks a suggestion in the message input box.
6027
+ * Fires when the user clicks a suggestion in the message input box.
4945
6028
  */
4946
6029
  suggestionExecute = new EventEmitter();
4947
6030
  /**
4948
- * Emits when the user selects a file in the message input box.
6031
+ * Fires when the user selects a file in the message input box.
4949
6032
  */
4950
6033
  fileSelect = new EventEmitter();
4951
6034
  /**
4952
- * Emits when the user removes a file from the message input box.
6035
+ * Fires when the user removes a file from the message input box.
4953
6036
  */
4954
6037
  fileRemove = new EventEmitter();
4955
6038
  /**
4956
- * Emits when the user unpins the pinned message.
6039
+ * Fires when the user unpins the pinned message.
4957
6040
  * This event triggers when the user clicks the delete button on the pinned message.
4958
6041
  */
4959
6042
  unpin = new EventEmitter();
4960
6043
  /**
4961
- * Emits when the user types in the message input box.
6044
+ * Fires when the user types in the message input box.
4962
6045
  */
4963
6046
  inputValueChange = new EventEmitter();
4964
6047
  get className() {
@@ -4967,13 +6050,23 @@ class ChatComponent {
4967
6050
  get dirAttr() {
4968
6051
  return this.direction;
4969
6052
  }
6053
+ set messagesContextMenu(contextMenu) {
6054
+ this.chatService.messagesContextMenu = contextMenu;
6055
+ }
4970
6056
  attachmentTemplate;
4971
6057
  chatHeaderTemplate;
6058
+ chatNoDataTemplate;
6059
+ authorMessageContentTemplate;
6060
+ receiverMessageContentTemplate;
6061
+ messageContentTemplate;
6062
+ authorMessageTemplate;
6063
+ receiverMessageTemplate;
4972
6064
  messageTemplate;
4973
6065
  timestampTemplate;
4974
6066
  suggestionTemplate;
4975
6067
  statusTemplate;
4976
6068
  messageBoxTemplate;
6069
+ userStatusTemplate;
4977
6070
  messageBox;
4978
6071
  /**
4979
6072
  * @hidden
@@ -4981,17 +6074,30 @@ class ChatComponent {
4981
6074
  messageList;
4982
6075
  /**
4983
6076
  * @hidden
4984
- * Returns processed messages when model fields are used, otherwise returns original messages
6077
+ * Returns processed messages when model fields are used, otherwise returns original messages.
4985
6078
  */
4986
6079
  get processedMessages() {
4987
6080
  if (!this.messages || this.messages.length === 0) {
4988
6081
  return [];
4989
6082
  }
4990
6083
  if (this._modelFields && Object.keys(this._modelFields).some(key => this._modelFields[key] !== defaultModelFields[key])) {
4991
- return processMessages(this.messages, this._modelFields);
6084
+ if (this.messages !== this._lastMessagesReference || this._modelFields !== this._lastModelFields) {
6085
+ this._cachedProcessedMessages = processMessages(this.messages, this._modelFields);
6086
+ this._lastMessagesReference = this.messages;
6087
+ this._lastModelFields = this._modelFields;
6088
+ }
6089
+ return this._cachedProcessedMessages;
4992
6090
  }
4993
6091
  return this.messages;
4994
6092
  }
6093
+ /**
6094
+ * Gets the actions available in the message context menu.
6095
+ *
6096
+ * @hidden
6097
+ */
6098
+ get contextMenuActions() {
6099
+ return transformActions(this.chatService.calculatedContextMenuActions);
6100
+ }
4995
6101
  /**
4996
6102
  * @hidden
4997
6103
  */
@@ -5019,6 +6125,9 @@ class ChatComponent {
5019
6125
  _modelFields = defaultModelFields;
5020
6126
  _messageContextMenuActions = CONTEXT_MENU_ACTIONS;
5021
6127
  _fileActions = FILE_ACTIONS;
6128
+ _cachedProcessedMessages = [];
6129
+ _lastMessagesReference = null;
6130
+ _lastModelFields = null;
5022
6131
  constructor(localization, zone, renderer, element, chatService) {
5023
6132
  this.localization = localization;
5024
6133
  this.zone = zone;
@@ -5036,6 +6145,9 @@ class ChatComponent {
5036
6145
  */
5037
6146
  ngOnInit() {
5038
6147
  this.chatService.messageWidthMode = this.messageWidthMode;
6148
+ this.chatService.timestampVisibility = this.timestampVisibility;
6149
+ this.chatService.showUsername = this.showUsername;
6150
+ this.chatService.showAvatar = this.showAvatar;
5039
6151
  this.chatService.allowMessageCollapse = this.allowMessageCollapse;
5040
6152
  this.chatService.enableSpeechToText = this.enableSpeechToText;
5041
6153
  this.chatService.sendButtonSettings = this.sendButtonSettings;
@@ -5043,7 +6155,8 @@ class ChatComponent {
5043
6155
  this.chatService.messageToolbarActions = this.messageToolbarActions;
5044
6156
  this.chatService.messageContextMenuActions = this.messageContextMenuActions;
5045
6157
  this.chatService.fileActions = this.fileActions;
5046
- this.chatService.messageToolbarVisibility = this.messageToolbarVisibility;
6158
+ this.chatService.authorMessageSettings = this.authorMessageSettings;
6159
+ this.chatService.receiverMessageSettings = this.receiverMessageSettings;
5047
6160
  this.chatService.messages = this.processedMessages || [];
5048
6161
  this.chatService.chatElement = this.messageList;
5049
6162
  this.subs.add(this.chatService.toolbarAction$.subscribe((actionEvent) => {
@@ -5084,6 +6197,9 @@ class ChatComponent {
5084
6197
  this.updateChatServiceProperties([
5085
6198
  'authorId',
5086
6199
  'messageWidthMode',
6200
+ 'timestampVisibility',
6201
+ 'showUsername',
6202
+ 'showAvatar',
5087
6203
  'allowMessageCollapse',
5088
6204
  'enableSpeechToText',
5089
6205
  'sendButtonSettings',
@@ -5091,7 +6207,8 @@ class ChatComponent {
5091
6207
  'messageToolbarActions',
5092
6208
  'messageContextMenuActions',
5093
6209
  'fileActions',
5094
- 'messageToolbarVisibility'
6210
+ 'authorMessageSettings',
6211
+ 'receiverMessageSettings'
5095
6212
  ], changes);
5096
6213
  }
5097
6214
  /**
@@ -5140,6 +6257,43 @@ class ChatComponent {
5140
6257
  this.chatService.scrollToMessage(this.pinnedMessage?.id);
5141
6258
  }
5142
6259
  }
6260
+ /**
6261
+ * @hidden
6262
+ */
6263
+ onContextMenuAction(action) {
6264
+ if (action.id === 'reply') {
6265
+ this.chatService.reply = this.chatService.activeMessage;
6266
+ }
6267
+ if (action.id === 'copy') {
6268
+ navigator.clipboard.writeText(this.chatService.activeMessage.text);
6269
+ }
6270
+ this.chatService.emit('contextMenuAction', { action, message: this.chatService.activeMessage });
6271
+ }
6272
+ /**
6273
+ * @hidden
6274
+ */
6275
+ handleMenuClose(event) {
6276
+ if (event) {
6277
+ const originalEvent = event.originalEvent;
6278
+ originalEvent && this.onActionButtonClick(originalEvent);
6279
+ }
6280
+ this.chatService.activeMessage = null;
6281
+ this.chatService.emit('contextMenuVisibilityChange', false);
6282
+ if (this.chatService.selectOnMenuClose) {
6283
+ this.chatService.activeMessageElement.selected = true;
6284
+ this.chatService.focusActiveMessageElement();
6285
+ }
6286
+ }
6287
+ /**
6288
+ * @hidden
6289
+ */
6290
+ onActionButtonClick(event) {
6291
+ const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
6292
+ const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
6293
+ if (clickOutsideMessage && !menuItemClick) {
6294
+ this.chatService.selectOnMenuClose = false;
6295
+ }
6296
+ }
5143
6297
  findLastPinnedMessage() {
5144
6298
  return [...this.processedMessages].reverse().find((message) => message.isPinned);
5145
6299
  }
@@ -5163,14 +6317,15 @@ class ChatComponent {
5163
6317
  });
5164
6318
  }
5165
6319
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComponent, deps: [{ token: i1.LocalizationService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: ChatService }], target: i0.ɵɵFactoryTarget.Component });
5166
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChatComponent, isStandalone: true, selector: "kendo-chat", inputs: { messages: "messages", authorId: "authorId", messageBoxType: "messageBoxType", height: "height", width: "width", placeholder: "placeholder", messageWidthMode: "messageWidthMode", allowMessageCollapse: "allowMessageCollapse", enableSpeechToText: "enableSpeechToText", enableFileSelect: "enableFileSelect", messageToolbarActions: "messageToolbarActions", inputValue: "inputValue", messageContextMenuActions: "messageContextMenuActions", fileActions: "fileActions", suggestions: "suggestions", messageToolbarVisibility: "messageToolbarVisibility", sendButtonSettings: "sendButtonSettings", modelFields: "modelFields" }, outputs: { sendMessage: "sendMessage", toolbarActionClick: "toolbarActionClick", contextMenuActionClick: "contextMenuActionClick", fileActionClick: "fileActionClick", download: "download", executeAction: "executeAction", suggestionExecute: "suggestionExecute", fileSelect: "fileSelect", fileRemove: "fileRemove", unpin: "unpin", inputValueChange: "inputValueChange" }, host: { properties: { "class": "this.className", "attr.dir": "this.dirAttr" } }, providers: [
6320
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChatComponent, isStandalone: true, selector: "kendo-chat", inputs: { messages: "messages", authorId: "authorId", messageBoxType: "messageBoxType", height: "height", width: "width", placeholder: "placeholder", messageWidthMode: "messageWidthMode", timestampVisibility: "timestampVisibility", showUsername: "showUsername", showAvatar: "showAvatar", allowMessageCollapse: "allowMessageCollapse", enableSpeechToText: "enableSpeechToText", enableFileSelect: "enableFileSelect", messageToolbarActions: "messageToolbarActions", inputValue: "inputValue", authorMessageSettings: "authorMessageSettings", receiverMessageSettings: "receiverMessageSettings", messageContextMenuActions: "messageContextMenuActions", fileActions: "fileActions", messageFilesLayout: "messageFilesLayout", suggestionsLayout: "suggestionsLayout", quickActionsLayout: "quickActionsLayout", suggestions: "suggestions", sendButtonSettings: "sendButtonSettings", modelFields: "modelFields" }, outputs: { sendMessage: "sendMessage", toolbarActionClick: "toolbarActionClick", contextMenuActionClick: "contextMenuActionClick", fileActionClick: "fileActionClick", download: "download", executeAction: "executeAction", suggestionExecute: "suggestionExecute", fileSelect: "fileSelect", fileRemove: "fileRemove", unpin: "unpin", inputValueChange: "inputValueChange" }, host: { properties: { "class": "this.className", "attr.dir": "this.dirAttr" } }, providers: [
5167
6321
  LocalizationService,
5168
6322
  ChatService,
6323
+ SuggestionsScrollService,
5169
6324
  {
5170
6325
  provide: L10N_PREFIX,
5171
6326
  useValue: 'kendo.chat'
5172
6327
  }
5173
- ], queries: [{ propertyName: "attachmentTemplate", first: true, predicate: AttachmentTemplateDirective, descendants: true }, { propertyName: "chatHeaderTemplate", first: true, predicate: ChatHeaderTemplateDirective, descendants: true }, { propertyName: "messageTemplate", first: true, predicate: MessageTemplateDirective, descendants: true }, { propertyName: "timestampTemplate", first: true, predicate: ChatTimestampTemplateDirective, descendants: true }, { propertyName: "suggestionTemplate", first: true, predicate: ChatSuggestionTemplateDirective, descendants: true }, { propertyName: "statusTemplate", first: true, predicate: ChatStatusTemplateDirective, descendants: true }, { propertyName: "messageBoxTemplate", first: true, predicate: ChatMessageBoxTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "messageBox", first: true, predicate: ["messageBox"], descendants: true }, { propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
6328
+ ], queries: [{ propertyName: "attachmentTemplate", first: true, predicate: AttachmentTemplateDirective, descendants: true }, { propertyName: "chatHeaderTemplate", first: true, predicate: ChatHeaderTemplateDirective, descendants: true }, { propertyName: "chatNoDataTemplate", first: true, predicate: NoDataTemplateDirective, descendants: true }, { propertyName: "authorMessageContentTemplate", first: true, predicate: AuthorMessageContentTemplateDirective, descendants: true }, { propertyName: "receiverMessageContentTemplate", first: true, predicate: ReceiverMessageContentTemplateDirective, descendants: true }, { propertyName: "messageContentTemplate", first: true, predicate: MessageContentTemplateDirective, descendants: true }, { propertyName: "authorMessageTemplate", first: true, predicate: AuthorMessageTemplateDirective, descendants: true }, { propertyName: "receiverMessageTemplate", first: true, predicate: ReceiverMessageTemplateDirective, descendants: true }, { propertyName: "messageTemplate", first: true, predicate: MessageTemplateDirective, descendants: true }, { propertyName: "timestampTemplate", first: true, predicate: ChatTimestampTemplateDirective, descendants: true }, { propertyName: "suggestionTemplate", first: true, predicate: ChatSuggestionTemplateDirective, descendants: true }, { propertyName: "statusTemplate", first: true, predicate: ChatStatusTemplateDirective, descendants: true }, { propertyName: "messageBoxTemplate", first: true, predicate: ChatMessageBoxTemplateDirective, descendants: true }, { propertyName: "userStatusTemplate", first: true, predicate: ChatUserStatusTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "messagesContextMenu", first: true, predicate: ["messagesContextMenu"], descendants: true }, { propertyName: "messageBox", first: true, predicate: ["messageBox"], descendants: true }, { propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
5174
6329
  <ng-container kendoChatLocalizedMessages
5175
6330
  i18n-deletedMessageSenderText="kendo.chat.deletedMessageSenderText|The text that is displayed when the sender deletes a message"
5176
6331
  deletedMessageSenderText="You removed this message."
@@ -5219,10 +6374,16 @@ class ChatComponent {
5219
6374
 
5220
6375
  i18n-downloadAllFilesText="kendo.chat.downloadAllFilesText|Sets the text that is displayed in the download section of the message."
5221
6376
  downloadAllFilesText="Download all"
6377
+
6378
+ i18n-previousSuggestionsButtonTitle="kendo.chat.previousSuggestionsButtonTitle|The title of the button that scrolls to the previous suggestions"
6379
+ previousSuggestionsButtonTitle="Scroll left"
6380
+
6381
+ i18n-nextSuggestionsButtonTitle="kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions"
6382
+ nextSuggestionsButtonTitle="Scroll right"
5222
6383
  >
5223
6384
  </ng-container>
5224
6385
 
5225
- <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky">
6386
+ <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky" themeColor="inherit">
5226
6387
  <ng-container *ngTemplateOutlet="chatHeaderTemplate.templateRef"></ng-container>
5227
6388
  </kendo-appbar>
5228
6389
  <div class="k-message-reference k-message-reference-receiver k-message-pinned" *ngIf="pinnedMessage" (click)="scrollToPinnedMessage()">
@@ -5238,7 +6399,7 @@ class ChatComponent {
5238
6399
  </div>
5239
6400
  <div
5240
6401
  #messageList
5241
- class="k-message-list k-avatars"
6402
+ class="k-message-list"
5242
6403
  aria-live="polite"
5243
6404
  role="log"
5244
6405
  kendoChatScrollAnchor
@@ -5246,19 +6407,32 @@ class ChatComponent {
5246
6407
  #anchor="scrollAnchor"
5247
6408
  [(autoScroll)]="autoScroll"
5248
6409
  >
5249
- <kendo-chat-message-list
5250
- [messages]="processedMessages"
5251
- [messageTemplate]="messageTemplate"
5252
- [timestampTemplate]="timestampTemplate"
5253
- [statusTemplate]="statusTemplate"
5254
- [localization]="localizationText"
5255
- [attachmentTemplate]="attachmentTemplate"
5256
- [authorId]="authorId"
5257
- (executeAction)="dispatchAction($event)"
5258
- (resize)="anchor.scrollToBottom()"
5259
- (navigate)="this.autoScroll = false"
5260
- >
5261
- </kendo-chat-message-list>
6410
+ <div *ngIf="processedMessages && processedMessages.length === 0; else chatMessageList" class="k-message-list-content k-message-list-content-empty">
6411
+ <ng-template
6412
+ [ngTemplateOutlet]="chatNoDataTemplate?.templateRef">
6413
+ </ng-template>
6414
+ </div>
6415
+ <ng-template #chatMessageList>
6416
+ <kendo-chat-message-list
6417
+ [messages]="processedMessages"
6418
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
6419
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
6420
+ [messageContentTemplate]="messageContentTemplate"
6421
+ [authorMessageTemplate]="authorMessageTemplate"
6422
+ [receiverMessageTemplate]="receiverMessageTemplate"
6423
+ [messageTemplate]="messageTemplate"
6424
+ [timestampTemplate]="timestampTemplate"
6425
+ [statusTemplate]="statusTemplate"
6426
+ [userStatusTemplate]="userStatusTemplate"
6427
+ [localization]="localizationText"
6428
+ [attachmentTemplate]="attachmentTemplate"
6429
+ [authorId]="authorId"
6430
+ (executeAction)="dispatchAction($event)"
6431
+ (resize)="anchor.scrollToBottom()"
6432
+ (navigate)="this.autoScroll = false"
6433
+ >
6434
+ </kendo-chat-message-list>
6435
+ </ng-template>
5262
6436
  </div>
5263
6437
  <kendo-message-box
5264
6438
  #messageBox
@@ -5276,7 +6450,16 @@ class ChatComponent {
5276
6450
  (fileRemove)="fileRemove.emit($event)"
5277
6451
  >
5278
6452
  </kendo-message-box>
5279
- `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$1, selector: "[kendoChatLocalizedMessages]" }, { kind: "directive", type: ScrollAnchorDirective, selector: "[kendoChatScrollAnchor]", inputs: ["autoScroll"], outputs: ["autoScrollChange"], exportAs: ["scrollAnchor"] }, { kind: "component", type: MessageListComponent, selector: "kendo-chat-message-list", inputs: ["messages", "attachmentTemplate", "messageTemplate", "timestampTemplate", "statusTemplate", "localization", "authorId"], outputs: ["executeAction", "navigate", "resize"] }, { kind: "component", type: MessageBoxComponent, selector: "kendo-message-box", inputs: ["authorId", "autoScroll", "suggestions", "placeholder", "inputValue", "localization", "messageBoxTemplate", "suggestionTemplate"], outputs: ["sendMessage", "executeSuggestion", "fileSelect", "fileRemove"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: AppBarComponent, selector: "kendo-appbar", inputs: ["position", "positionMode", "themeColor"], exportAs: ["kendoAppBar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
6453
+
6454
+ <kendo-contextmenu
6455
+ #messagesContextMenu
6456
+ [items]="contextMenuActions"
6457
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
6458
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
6459
+ (popupClose)="handleMenuClose($event)"
6460
+ (select)="onContextMenuAction($event.item.originalAction)"
6461
+ ></kendo-contextmenu>
6462
+ `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$1, selector: "[kendoChatLocalizedMessages]" }, { kind: "directive", type: ScrollAnchorDirective, selector: "[kendoChatScrollAnchor]", inputs: ["autoScroll"], outputs: ["autoScrollChange"], exportAs: ["scrollAnchor"] }, { kind: "component", type: MessageListComponent, selector: "kendo-chat-message-list", inputs: ["messages", "attachmentTemplate", "authorMessageContentTemplate", "receiverMessageContentTemplate", "messageContentTemplate", "authorMessageTemplate", "receiverMessageTemplate", "messageTemplate", "timestampTemplate", "statusTemplate", "userStatusTemplate", "localization", "authorId"], outputs: ["executeAction", "navigate", "resize"] }, { kind: "component", type: MessageBoxComponent, selector: "kendo-message-box", inputs: ["authorId", "autoScroll", "suggestions", "placeholder", "inputValue", "localization", "messageBoxTemplate", "suggestionTemplate"], outputs: ["sendMessage", "executeSuggestion", "fileSelect", "fileRemove"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: AppBarComponent, selector: "kendo-appbar", inputs: ["position", "positionMode", "themeColor"], exportAs: ["kendoAppBar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }] });
5280
6463
  }
5281
6464
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComponent, decorators: [{
5282
6465
  type: Component,
@@ -5284,6 +6467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5284
6467
  providers: [
5285
6468
  LocalizationService,
5286
6469
  ChatService,
6470
+ SuggestionsScrollService,
5287
6471
  {
5288
6472
  provide: L10N_PREFIX,
5289
6473
  useValue: 'kendo.chat'
@@ -5339,10 +6523,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5339
6523
 
5340
6524
  i18n-downloadAllFilesText="kendo.chat.downloadAllFilesText|Sets the text that is displayed in the download section of the message."
5341
6525
  downloadAllFilesText="Download all"
6526
+
6527
+ i18n-previousSuggestionsButtonTitle="kendo.chat.previousSuggestionsButtonTitle|The title of the button that scrolls to the previous suggestions"
6528
+ previousSuggestionsButtonTitle="Scroll left"
6529
+
6530
+ i18n-nextSuggestionsButtonTitle="kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions"
6531
+ nextSuggestionsButtonTitle="Scroll right"
5342
6532
  >
5343
6533
  </ng-container>
5344
6534
 
5345
- <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky">
6535
+ <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky" themeColor="inherit">
5346
6536
  <ng-container *ngTemplateOutlet="chatHeaderTemplate.templateRef"></ng-container>
5347
6537
  </kendo-appbar>
5348
6538
  <div class="k-message-reference k-message-reference-receiver k-message-pinned" *ngIf="pinnedMessage" (click)="scrollToPinnedMessage()">
@@ -5358,7 +6548,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5358
6548
  </div>
5359
6549
  <div
5360
6550
  #messageList
5361
- class="k-message-list k-avatars"
6551
+ class="k-message-list"
5362
6552
  aria-live="polite"
5363
6553
  role="log"
5364
6554
  kendoChatScrollAnchor
@@ -5366,19 +6556,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5366
6556
  #anchor="scrollAnchor"
5367
6557
  [(autoScroll)]="autoScroll"
5368
6558
  >
5369
- <kendo-chat-message-list
5370
- [messages]="processedMessages"
5371
- [messageTemplate]="messageTemplate"
5372
- [timestampTemplate]="timestampTemplate"
5373
- [statusTemplate]="statusTemplate"
5374
- [localization]="localizationText"
5375
- [attachmentTemplate]="attachmentTemplate"
5376
- [authorId]="authorId"
5377
- (executeAction)="dispatchAction($event)"
5378
- (resize)="anchor.scrollToBottom()"
5379
- (navigate)="this.autoScroll = false"
5380
- >
5381
- </kendo-chat-message-list>
6559
+ <div *ngIf="processedMessages && processedMessages.length === 0; else chatMessageList" class="k-message-list-content k-message-list-content-empty">
6560
+ <ng-template
6561
+ [ngTemplateOutlet]="chatNoDataTemplate?.templateRef">
6562
+ </ng-template>
6563
+ </div>
6564
+ <ng-template #chatMessageList>
6565
+ <kendo-chat-message-list
6566
+ [messages]="processedMessages"
6567
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
6568
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
6569
+ [messageContentTemplate]="messageContentTemplate"
6570
+ [authorMessageTemplate]="authorMessageTemplate"
6571
+ [receiverMessageTemplate]="receiverMessageTemplate"
6572
+ [messageTemplate]="messageTemplate"
6573
+ [timestampTemplate]="timestampTemplate"
6574
+ [statusTemplate]="statusTemplate"
6575
+ [userStatusTemplate]="userStatusTemplate"
6576
+ [localization]="localizationText"
6577
+ [attachmentTemplate]="attachmentTemplate"
6578
+ [authorId]="authorId"
6579
+ (executeAction)="dispatchAction($event)"
6580
+ (resize)="anchor.scrollToBottom()"
6581
+ (navigate)="this.autoScroll = false"
6582
+ >
6583
+ </kendo-chat-message-list>
6584
+ </ng-template>
5382
6585
  </div>
5383
6586
  <kendo-message-box
5384
6587
  #messageBox
@@ -5396,9 +6599,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5396
6599
  (fileRemove)="fileRemove.emit($event)"
5397
6600
  >
5398
6601
  </kendo-message-box>
6602
+
6603
+ <kendo-contextmenu
6604
+ #messagesContextMenu
6605
+ [items]="contextMenuActions"
6606
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
6607
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
6608
+ (popupClose)="handleMenuClose($event)"
6609
+ (select)="onContextMenuAction($event.item.originalAction)"
6610
+ ></kendo-contextmenu>
5399
6611
  `,
5400
6612
  standalone: true,
5401
- imports: [LocalizedMessagesDirective$1, ScrollAnchorDirective, MessageListComponent, MessageBoxComponent, MessageReferenceComponent, AppBarComponent, NgTemplateOutlet, NgIf, IconWrapperComponent, KENDO_BUTTON]
6613
+ imports: [LocalizedMessagesDirective$1, ScrollAnchorDirective, MessageListComponent, MessageBoxComponent, MessageReferenceComponent, AppBarComponent, NgTemplateOutlet, NgIf, IconWrapperComponent, KENDO_BUTTON, ContextMenuComponent]
5402
6614
  }]
5403
6615
  }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: ChatService }], propDecorators: { messages: [{
5404
6616
  type: Input
@@ -5414,6 +6626,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5414
6626
  type: Input
5415
6627
  }], messageWidthMode: [{
5416
6628
  type: Input
6629
+ }], timestampVisibility: [{
6630
+ type: Input
6631
+ }], showUsername: [{
6632
+ type: Input
6633
+ }], showAvatar: [{
6634
+ type: Input
5417
6635
  }], allowMessageCollapse: [{
5418
6636
  type: Input
5419
6637
  }], enableSpeechToText: [{
@@ -5424,13 +6642,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5424
6642
  type: Input
5425
6643
  }], inputValue: [{
5426
6644
  type: Input
6645
+ }], authorMessageSettings: [{
6646
+ type: Input
6647
+ }], receiverMessageSettings: [{
6648
+ type: Input
5427
6649
  }], messageContextMenuActions: [{
5428
6650
  type: Input
5429
6651
  }], fileActions: [{
5430
6652
  type: Input
5431
- }], suggestions: [{
6653
+ }], messageFilesLayout: [{
6654
+ type: Input
6655
+ }], suggestionsLayout: [{
5432
6656
  type: Input
5433
- }], messageToolbarVisibility: [{
6657
+ }], quickActionsLayout: [{
6658
+ type: Input
6659
+ }], suggestions: [{
5434
6660
  type: Input
5435
6661
  }], sendButtonSettings: [{
5436
6662
  type: Input
@@ -5464,12 +6690,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5464
6690
  }], dirAttr: [{
5465
6691
  type: HostBinding,
5466
6692
  args: ['attr.dir']
6693
+ }], messagesContextMenu: [{
6694
+ type: ViewChild,
6695
+ args: ['messagesContextMenu']
5467
6696
  }], attachmentTemplate: [{
5468
6697
  type: ContentChild,
5469
6698
  args: [AttachmentTemplateDirective]
5470
6699
  }], chatHeaderTemplate: [{
5471
6700
  type: ContentChild,
5472
6701
  args: [ChatHeaderTemplateDirective]
6702
+ }], chatNoDataTemplate: [{
6703
+ type: ContentChild,
6704
+ args: [NoDataTemplateDirective]
6705
+ }], authorMessageContentTemplate: [{
6706
+ type: ContentChild,
6707
+ args: [AuthorMessageContentTemplateDirective]
6708
+ }], receiverMessageContentTemplate: [{
6709
+ type: ContentChild,
6710
+ args: [ReceiverMessageContentTemplateDirective]
6711
+ }], messageContentTemplate: [{
6712
+ type: ContentChild,
6713
+ args: [MessageContentTemplateDirective]
6714
+ }], authorMessageTemplate: [{
6715
+ type: ContentChild,
6716
+ args: [AuthorMessageTemplateDirective]
6717
+ }], receiverMessageTemplate: [{
6718
+ type: ContentChild,
6719
+ args: [ReceiverMessageTemplateDirective]
5473
6720
  }], messageTemplate: [{
5474
6721
  type: ContentChild,
5475
6722
  args: [MessageTemplateDirective]
@@ -5485,6 +6732,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5485
6732
  }], messageBoxTemplate: [{
5486
6733
  type: ContentChild,
5487
6734
  args: [ChatMessageBoxTemplateDirective]
6735
+ }], userStatusTemplate: [{
6736
+ type: ContentChild,
6737
+ args: [ChatUserStatusTemplateDirective]
5488
6738
  }], messageBox: [{
5489
6739
  type: ViewChild,
5490
6740
  args: ['messageBox']
@@ -6724,13 +7974,20 @@ const KENDO_CHAT = [
6724
7974
  ChatComponent,
6725
7975
  CustomMessagesComponent,
6726
7976
  AttachmentTemplateDirective,
7977
+ AuthorMessageContentTemplateDirective,
7978
+ ReceiverMessageContentTemplateDirective,
7979
+ MessageContentTemplateDirective,
7980
+ AuthorMessageTemplateDirective,
7981
+ ReceiverMessageTemplateDirective,
6727
7982
  MessageTemplateDirective,
6728
7983
  HeroCardComponent,
6729
7984
  ChatMessageBoxTemplateDirective,
6730
7985
  ChatHeaderTemplateDirective,
7986
+ NoDataTemplateDirective,
6731
7987
  ChatTimestampTemplateDirective,
6732
7988
  ChatStatusTemplateDirective,
6733
- ChatSuggestionTemplateDirective
7989
+ ChatSuggestionTemplateDirective,
7990
+ ChatUserStatusTemplateDirective
6734
7991
  ];
6735
7992
  /**
6736
7993
  * Utility array that contains all InlineAIPrompt related components and directives.
@@ -6837,7 +8094,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6837
8094
  */
6838
8095
  class ChatModule {
6839
8096
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
6840
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, imports: [ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective], exports: [ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective] });
8097
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, imports: [ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, AuthorMessageContentTemplateDirective, ReceiverMessageContentTemplateDirective, MessageContentTemplateDirective, AuthorMessageTemplateDirective, ReceiverMessageTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, NoDataTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatUserStatusTemplateDirective], exports: [ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, AuthorMessageContentTemplateDirective, ReceiverMessageContentTemplateDirective, MessageContentTemplateDirective, AuthorMessageTemplateDirective, ReceiverMessageTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, NoDataTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatUserStatusTemplateDirective] });
6841
8098
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, providers: [IconsService, ResizeBatchService], imports: [ChatComponent, HeroCardComponent] });
6842
8099
  }
6843
8100
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, decorators: [{
@@ -6870,7 +8127,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6870
8127
  */
6871
8128
  class ConversationalUIModule {
6872
8129
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
6873
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, imports: [AIPromptComponent, PromptViewComponent, OutputViewComponent, CommandViewComponent, CustomViewComponent, AIPromptCustomMessagesComponent, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AIPromptOutputTemplateDirective, AIPromptOutputBodyTemplateDirective, ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, InlineAIPromptComponent, InlineAIPromptOutputTemplateDirective, InlineAIPromptCustomMessagesComponent], exports: [AIPromptComponent, PromptViewComponent, OutputViewComponent, CommandViewComponent, CustomViewComponent, AIPromptCustomMessagesComponent, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AIPromptOutputTemplateDirective, AIPromptOutputBodyTemplateDirective, ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, InlineAIPromptComponent, InlineAIPromptOutputTemplateDirective, InlineAIPromptCustomMessagesComponent] });
8130
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, imports: [AIPromptComponent, PromptViewComponent, OutputViewComponent, CommandViewComponent, CustomViewComponent, AIPromptCustomMessagesComponent, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AIPromptOutputTemplateDirective, AIPromptOutputBodyTemplateDirective, ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, AuthorMessageContentTemplateDirective, ReceiverMessageContentTemplateDirective, MessageContentTemplateDirective, AuthorMessageTemplateDirective, ReceiverMessageTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, NoDataTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatUserStatusTemplateDirective, InlineAIPromptComponent, InlineAIPromptOutputTemplateDirective, InlineAIPromptCustomMessagesComponent], exports: [AIPromptComponent, PromptViewComponent, OutputViewComponent, CommandViewComponent, CustomViewComponent, AIPromptCustomMessagesComponent, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AIPromptOutputTemplateDirective, AIPromptOutputBodyTemplateDirective, ChatComponent, CustomMessagesComponent, AttachmentTemplateDirective, AuthorMessageContentTemplateDirective, ReceiverMessageContentTemplateDirective, MessageContentTemplateDirective, AuthorMessageTemplateDirective, ReceiverMessageTemplateDirective, MessageTemplateDirective, HeroCardComponent, ChatMessageBoxTemplateDirective, ChatHeaderTemplateDirective, NoDataTemplateDirective, ChatTimestampTemplateDirective, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatUserStatusTemplateDirective, InlineAIPromptComponent, InlineAIPromptOutputTemplateDirective, InlineAIPromptCustomMessagesComponent] });
6874
8131
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, providers: [IconsService, PopupService, ResizeBatchService, DialogContainerService, DialogService, WindowService, WindowContainerService], imports: [AIPromptComponent, PromptViewComponent, OutputViewComponent, CommandViewComponent, ChatComponent, HeroCardComponent, InlineAIPromptComponent] });
6875
8132
  }
6876
8133
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, decorators: [{
@@ -7058,5 +8315,5 @@ class InlineAIPromptSettings {
7058
8315
  * Generated bundle index. Do not edit.
7059
8316
  */
7060
8317
 
7061
- export { AIPromptComponent, AIPromptCustomMessagesComponent, AIPromptModule, AIPromptOutputBodyTemplateDirective, AIPromptOutputTemplateDirective, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AttachmentTemplateDirective, ChatComponent, ChatHeaderTemplateDirective, ChatMessageBoxTemplateDirective, ChatModule, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatTimestampTemplateDirective, CommandViewComponent, ConversationalUIModule, CustomMessagesComponent, CustomViewComponent, ExecuteActionEvent, HeroCardComponent, InlineAIPromptComponent, InlineAIPromptCustomMessagesComponent, InlineAIPromptModule, InlineAIPromptOutputTemplateDirective, InlineAIPromptService, InlineAIPromptSettings, KENDO_AIPROMPT, KENDO_CHAT, KENDO_CONVERSATIONALUI, KENDO_INLINEAIPROMPT, MessageTemplateDirective, OutputViewComponent, PromptViewComponent, SendMessageEvent };
8318
+ export { AIPromptComponent, AIPromptCustomMessagesComponent, AIPromptModule, AIPromptOutputBodyTemplateDirective, AIPromptOutputTemplateDirective, AIPromptToolbarActionsDirective, AIPromptToolbarFocusableDirective, AttachmentTemplateDirective, AuthorMessageContentTemplateDirective, AuthorMessageTemplateDirective, ChatComponent, ChatHeaderTemplateDirective, ChatMessageBoxTemplateDirective, ChatModule, ChatStatusTemplateDirective, ChatSuggestionTemplateDirective, ChatTimestampTemplateDirective, ChatUserStatusTemplateDirective, CommandViewComponent, ConversationalUIModule, CustomMessagesComponent, CustomViewComponent, ExecuteActionEvent, HeroCardComponent, InlineAIPromptComponent, InlineAIPromptCustomMessagesComponent, InlineAIPromptModule, InlineAIPromptOutputTemplateDirective, InlineAIPromptService, InlineAIPromptSettings, KENDO_AIPROMPT, KENDO_CHAT, KENDO_CONVERSATIONALUI, KENDO_INLINEAIPROMPT, MessageContentTemplateDirective, MessageTemplateDirective, NoDataTemplateDirective, OutputViewComponent, PromptViewComponent, ReceiverMessageContentTemplateDirective, ReceiverMessageTemplateDirective, SendMessageEvent };
7062
8319