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

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 (62) hide show
  1. package/chat/api/files-layout.d.ts +12 -0
  2. package/chat/api/index.d.ts +3 -1
  3. package/chat/api/message-settings.interface.d.ts +33 -0
  4. package/chat/api/message.interface.d.ts +5 -1
  5. package/chat/api/suggestions-layout.d.ts +20 -0
  6. package/chat/chat.component.d.ts +92 -34
  7. package/chat/chat.directives.d.ts +18 -0
  8. package/chat/chat.module.d.ts +15 -8
  9. package/chat/common/chat.service.d.ts +32 -3
  10. package/chat/common/models/model-fields.d.ts +0 -6
  11. package/chat/common/scroll-button.component.d.ts +32 -0
  12. package/chat/common/scroll.service.d.ts +39 -0
  13. package/chat/common/utils.d.ts +13 -1
  14. package/chat/l10n/messages.d.ts +9 -1
  15. package/chat/message-list.component.d.ts +20 -3
  16. package/chat/message.component.d.ts +40 -16
  17. package/chat/suggested-actions.component.d.ts +30 -5
  18. package/chat/templates/author-message-content-template.directive.d.ts +28 -0
  19. package/chat/templates/author-message-template.directive.d.ts +28 -0
  20. package/chat/templates/message-content-template.directive.d.ts +28 -0
  21. package/chat/templates/message-template.directive.d.ts +1 -1
  22. package/chat/templates/no-data-template.directive.d.ts +27 -0
  23. package/chat/templates/receiver-message-content-template.directive.d.ts +28 -0
  24. package/chat/templates/receiver-message-template.directive.d.ts +28 -0
  25. package/chat/templates/user-status-template.directive.d.ts +27 -0
  26. package/conversational-ui.module.d.ts +18 -11
  27. package/directives.d.ts +9 -2
  28. package/esm2022/chat/api/index.mjs +3 -1
  29. package/{chat/api/message-toolbar-visibility.d.ts → esm2022/chat/api/message-settings.interface.mjs} +1 -4
  30. package/esm2022/chat/api/suggestions-layout.mjs +5 -0
  31. package/esm2022/chat/builtin-actions.mjs +2 -0
  32. package/esm2022/chat/chat-file.component.mjs +2 -2
  33. package/esm2022/chat/chat.component.mjs +265 -71
  34. package/esm2022/chat/chat.directives.mjs +18 -0
  35. package/esm2022/chat/chat.module.mjs +16 -9
  36. package/esm2022/chat/common/chat.service.mjs +83 -4
  37. package/esm2022/chat/common/models/default-model-fields.mjs +0 -1
  38. package/esm2022/chat/common/scroll-button.component.mjs +81 -0
  39. package/esm2022/chat/common/scroll.service.mjs +110 -0
  40. package/esm2022/chat/common/utils.mjs +22 -3
  41. package/esm2022/chat/l10n/messages.mjs +13 -1
  42. package/esm2022/chat/message-attachments.component.mjs +2 -2
  43. package/esm2022/chat/message-box.component.mjs +5 -2
  44. package/esm2022/chat/message-list.component.mjs +165 -19
  45. package/esm2022/chat/message.component.mjs +487 -326
  46. package/esm2022/chat/suggested-actions.component.mjs +298 -80
  47. package/esm2022/chat/templates/author-message-content-template.directive.mjs +39 -0
  48. package/esm2022/chat/templates/author-message-template.directive.mjs +39 -0
  49. package/esm2022/chat/templates/message-content-template.directive.mjs +39 -0
  50. package/esm2022/chat/templates/message-template.directive.mjs +1 -1
  51. package/esm2022/chat/templates/no-data-template.directive.mjs +38 -0
  52. package/esm2022/chat/templates/receiver-message-content-template.directive.mjs +39 -0
  53. package/esm2022/chat/templates/receiver-message-template.directive.mjs +39 -0
  54. package/esm2022/chat/templates/user-status-template.directive.mjs +38 -0
  55. package/esm2022/conversational-ui.module.mjs +19 -12
  56. package/esm2022/directives.mjs +15 -1
  57. package/esm2022/index.mjs +7 -0
  58. package/esm2022/package-metadata.mjs +2 -2
  59. package/fesm2022/progress-kendo-angular-conversational-ui.mjs +1771 -547
  60. package/index.d.ts +7 -0
  61. package/package.json +14 -14
  62. /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: 1761910356,
220
+ version: '21.0.0-develop.9',
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,42 @@ 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';
2144
2134
  _enableSpeechToText = STB_DEFAULT_SETTINGS;
2145
2135
  _enableFileSelect = FILESELECT_DEFAULT_SETTINGS;
2146
2136
  _sendButtonSettings = SEND_BTN_DEFAULT_SETTINGS;
2137
+ _suggestionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2138
+ _quickActionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2139
+ _authorMessageSettings;
2140
+ _receiverMessageSettings;
2141
+ _allowMessageCollapse;
2147
2142
  subjects = {
2148
2143
  toolbarAction: new Subject(),
2149
2144
  contextMenuAction: new Subject(),
2150
2145
  fileAction: new Subject(),
2151
2146
  fileDownload: new Subject(),
2152
2147
  replyReferenceClick: new Subject(),
2153
- inputValueChange: new Subject()
2148
+ inputValueChange: new Subject(),
2149
+ contextMenuVisibilityChange: new Subject(),
2150
+ suggestionsLayoutChange: new Subject(),
2151
+ quickActionsLayoutChange: new Subject(),
2152
+ authorMessageSettingsChange: new Subject(),
2153
+ receiverMessageSettingsChange: new Subject(),
2154
+ allowMessageCollapseChange: new Subject(),
2154
2155
  };
2155
2156
  toolbarAction$ = this.subjects.toolbarAction.asObservable();
2156
2157
  contextMenuAction$ = this.subjects.contextMenuAction.asObservable();
@@ -2158,6 +2159,32 @@ class ChatService {
2158
2159
  fileDownload$ = this.subjects.fileDownload.asObservable();
2159
2160
  replyReferenceClick$ = this.subjects.replyReferenceClick.asObservable();
2160
2161
  inputValueChange$ = this.subjects.inputValueChange.asObservable();
2162
+ contextMenuVisibilityChange$ = this.subjects.contextMenuVisibilityChange.asObservable();
2163
+ suggestionsLayoutChange$ = this.subjects.suggestionsLayoutChange.asObservable();
2164
+ quickActionsLayoutChange$ = this.subjects.quickActionsLayoutChange.asObservable();
2165
+ authorMessageSettingsChange$ = this.subjects.authorMessageSettingsChange.asObservable();
2166
+ receiverMessageSettingsChange$ = this.subjects.receiverMessageSettingsChange.asObservable();
2167
+ allowMessageCollapseChange$ = this.subjects.allowMessageCollapseChange.asObservable();
2168
+ set authorMessageSettings(settings) {
2169
+ const previousSettings = this._authorMessageSettings;
2170
+ if (JSON.stringify(previousSettings) !== JSON.stringify(settings)) {
2171
+ this.updateComponentSettings('_authorMessageSettings', settings, null);
2172
+ this.emit('authorMessageSettingsChange', this._authorMessageSettings);
2173
+ }
2174
+ }
2175
+ get authorMessageSettings() {
2176
+ return this._authorMessageSettings;
2177
+ }
2178
+ set receiverMessageSettings(settings) {
2179
+ const previousSettings = this._receiverMessageSettings;
2180
+ if (JSON.stringify(previousSettings) !== JSON.stringify(settings)) {
2181
+ this.updateComponentSettings('_receiverMessageSettings', settings, null);
2182
+ this.emit('receiverMessageSettingsChange', this._receiverMessageSettings);
2183
+ }
2184
+ }
2185
+ get receiverMessageSettings() {
2186
+ return this._receiverMessageSettings;
2187
+ }
2161
2188
  set enableSpeechToText(settings) {
2162
2189
  this.updateComponentSettings('_enableSpeechToText', settings, STB_DEFAULT_SETTINGS);
2163
2190
  }
@@ -2176,6 +2203,38 @@ class ChatService {
2176
2203
  get sendButtonSettings() {
2177
2204
  return this._sendButtonSettings;
2178
2205
  }
2206
+ set suggestionsLayout(layoutMode) {
2207
+ this._suggestionsLayout = layoutMode;
2208
+ this.emit('suggestionsLayoutChange', this._suggestionsLayout);
2209
+ }
2210
+ get suggestionsLayout() {
2211
+ return this._suggestionsLayout;
2212
+ }
2213
+ set quickActionsLayout(layoutMode) {
2214
+ this._quickActionsLayout = layoutMode;
2215
+ this.emit('quickActionsLayoutChange', this._quickActionsLayout);
2216
+ }
2217
+ get quickActionsLayout() {
2218
+ return this._quickActionsLayout;
2219
+ }
2220
+ set allowMessageCollapse(value) {
2221
+ const previousValue = this._allowMessageCollapse;
2222
+ if (previousValue !== value) {
2223
+ this._allowMessageCollapse = value;
2224
+ this.emit('allowMessageCollapseChange', this._allowMessageCollapse);
2225
+ }
2226
+ }
2227
+ get allowMessageCollapse() {
2228
+ return this._allowMessageCollapse;
2229
+ }
2230
+ calculateContextMenuActions(isOwn) {
2231
+ const settings = isOwn ? this.authorMessageSettings : this.receiverMessageSettings;
2232
+ if (settings?.messageContextMenuActions) {
2233
+ this.calculatedContextMenuActions = settings.messageContextMenuActions;
2234
+ return;
2235
+ }
2236
+ this.calculatedContextMenuActions = this.messageContextMenuActions || [];
2237
+ }
2179
2238
  emit(subjectKey, value) {
2180
2239
  (this.subjects[subjectKey]).next(value);
2181
2240
  }
@@ -2197,6 +2256,11 @@ class ChatService {
2197
2256
  elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
2198
2257
  }
2199
2258
  }
2259
+ focusActiveMessageElement() {
2260
+ if (this.activeMessageElement) {
2261
+ this.activeMessageElement.element?.nativeElement?.focus();
2262
+ }
2263
+ }
2200
2264
  updateComponentSettings(property, settings, defaultSettings) {
2201
2265
  if (settings === true) {
2202
2266
  this[property] = defaultSettings;
@@ -2253,24 +2317,238 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2253
2317
  }]
2254
2318
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
2255
2319
 
2320
+ const DIRECTION_CLASSES = {
2321
+ left: 'chevron-left',
2322
+ right: 'chevron-right',
2323
+ };
2324
+ /**
2325
+ * @hidden
2326
+ */
2327
+ class ChatScrollableButtonComponent {
2328
+ host;
2329
+ renderer;
2330
+ ngZone;
2331
+ localization;
2332
+ role = 'button';
2333
+ prev = false;
2334
+ onClick = new EventEmitter();
2335
+ get scrollButtonIconClass() {
2336
+ const defaultPrevIcon = !this.localization.rtl ? DIRECTION_CLASSES.left : DIRECTION_CLASSES.right;
2337
+ const defaultNextIcon = !this.localization.rtl ? DIRECTION_CLASSES.right : DIRECTION_CLASSES.left;
2338
+ return this.prev ? defaultPrevIcon : defaultNextIcon;
2339
+ }
2340
+ get scrollButtonSVGIcon() {
2341
+ const defaultPrevSVGIcon = !this.localization.rtl ? this.chevronLeftIcon : this.chevronRightIcon;
2342
+ const defaultNextSVGIcon = !this.localization.rtl ? this.chevronRightIcon : this.chevronLeftIcon;
2343
+ return this.prev ? defaultPrevSVGIcon : defaultNextSVGIcon;
2344
+ }
2345
+ chevronLeftIcon = chevronLeftIcon;
2346
+ chevronRightIcon = chevronRightIcon;
2347
+ subs = new Subscription();
2348
+ constructor(host, renderer, ngZone, localization) {
2349
+ this.host = host;
2350
+ this.renderer = renderer;
2351
+ this.ngZone = ngZone;
2352
+ this.localization = localization;
2353
+ }
2354
+ ngAfterViewInit() {
2355
+ this.ngZone.runOutsideAngular(() => {
2356
+ this.subs.add(this.renderer.listen(this.host.nativeElement, 'click', () => this.clickHandler()));
2357
+ });
2358
+ }
2359
+ ngOnDestroy() {
2360
+ this.subs.unsubscribe();
2361
+ }
2362
+ clickHandler() {
2363
+ const buttonType = this.prev ? 'prev' : 'next';
2364
+ this.onClick.emit(buttonType);
2365
+ }
2366
+ 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 });
2367
+ 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: `
2368
+ <kendo-icon-wrapper [name]="scrollButtonIconClass" [svgIcon]="scrollButtonSVGIcon" innerCssClass="k-button-icon"> </kendo-icon-wrapper>
2369
+ `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] });
2370
+ }
2371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatScrollableButtonComponent, decorators: [{
2372
+ type: Component,
2373
+ args: [{
2374
+ template: `
2375
+ <kendo-icon-wrapper [name]="scrollButtonIconClass" [svgIcon]="scrollButtonSVGIcon" innerCssClass="k-button-icon"> </kendo-icon-wrapper>
2376
+ `,
2377
+ // eslint-disable-next-line @angular-eslint/component-selector
2378
+ selector: '[kendoChatScrollableButton]',
2379
+ standalone: true,
2380
+ imports: [IconWrapperComponent],
2381
+ }]
2382
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }], propDecorators: { role: [{
2383
+ type: HostBinding,
2384
+ args: ['attr.role']
2385
+ }], prev: [{
2386
+ type: Input
2387
+ }], onClick: [{
2388
+ type: Output
2389
+ }] } });
2390
+
2391
+ const DEFAULT_SCROLL_BEHAVIOR = 'smooth';
2392
+ const DEFAULT_SCROLL_SPEED = 100;
2393
+ /**
2394
+ * @hidden
2395
+ */
2396
+ class SuggestionsScrollService {
2397
+ ngZone;
2398
+ localization;
2399
+ owner;
2400
+ position = 0;
2401
+ scrollButtonActiveStateChange = new Subject();
2402
+ get scrollElement() {
2403
+ return this.owner.suggestionsContainer?.nativeElement;
2404
+ }
2405
+ get scrollContainerOverflowSize() {
2406
+ if (!isDocumentAvailable()) {
2407
+ return 0;
2408
+ }
2409
+ if (!this.scrollElement) {
2410
+ return 0;
2411
+ }
2412
+ const overflowSize = Math.floor(this.scrollElement.scrollWidth - this.scrollElement.offsetWidth);
2413
+ return overflowSize < 0 ? 0 : overflowSize;
2414
+ }
2415
+ get suggestionsOverflow() {
2416
+ return this.scrollContainerOverflowSize > 0;
2417
+ }
2418
+ constructor(ngZone, localization) {
2419
+ this.ngZone = ngZone;
2420
+ this.localization = localization;
2421
+ }
2422
+ toggleScrollButtonsState() {
2423
+ const suggestedActions = this.owner;
2424
+ if (!suggestedActions?.hasScrollButtons) {
2425
+ return;
2426
+ }
2427
+ const currentPrevButtonActive = !this.isDisabled('prev');
2428
+ const currentNextButtonActive = !this.isDisabled('next');
2429
+ const defaultOffset = 1;
2430
+ const rtlDelta = this.localization.rtl ? -1 : 1;
2431
+ const calculatedPrevButtonActive = (this.position * rtlDelta) > 0 && this.scrollContainerOverflowSize > 0;
2432
+ const calculatedNextButtonActive = (this.position * rtlDelta) < this.scrollContainerOverflowSize - defaultOffset && this.scrollContainerOverflowSize > 0;
2433
+ if (calculatedPrevButtonActive !== currentPrevButtonActive) {
2434
+ this.ngZone.run(() => this.toggleButtonActiveState('prev', calculatedPrevButtonActive));
2435
+ }
2436
+ if (calculatedNextButtonActive !== currentNextButtonActive) {
2437
+ this.ngZone.run(() => this.toggleButtonActiveState('next', calculatedNextButtonActive));
2438
+ }
2439
+ }
2440
+ onScroll(e) {
2441
+ this.position = e.target.scrollLeft;
2442
+ this.toggleScrollButtonsState();
2443
+ }
2444
+ scrollSuggestions(direction) {
2445
+ this.calculateListPosition(direction, DEFAULT_SCROLL_SPEED);
2446
+ if (this.scrollElement) {
2447
+ this.scrollElement.scrollTo({ left: this.position, behavior: DEFAULT_SCROLL_BEHAVIOR });
2448
+ }
2449
+ this.toggleScrollButtonsState();
2450
+ }
2451
+ updateScrollPosition(element) {
2452
+ this.position = element.scrollLeft;
2453
+ }
2454
+ calculateListPosition(direction, scrollSpeed) {
2455
+ if (direction === 'prev') {
2456
+ if (!this.localization.rtl) {
2457
+ this.position = this.position - scrollSpeed <= 0 ? 0 : this.position - scrollSpeed;
2458
+ }
2459
+ else {
2460
+ this.position = this.position + scrollSpeed >= 0 ? 0 : this.position + scrollSpeed;
2461
+ }
2462
+ }
2463
+ else if (direction === 'next' && this.position < this.scrollContainerOverflowSize) {
2464
+ if (this.position + scrollSpeed > this.scrollContainerOverflowSize) {
2465
+ if (this.localization.rtl) {
2466
+ this.position = -this.scrollContainerOverflowSize;
2467
+ }
2468
+ else {
2469
+ this.position = this.scrollContainerOverflowSize;
2470
+ }
2471
+ return;
2472
+ }
2473
+ if (this.localization.rtl) {
2474
+ this.position -= scrollSpeed;
2475
+ }
2476
+ else {
2477
+ this.position += scrollSpeed;
2478
+ }
2479
+ }
2480
+ }
2481
+ toggleButtonActiveState(buttonType, active) {
2482
+ this.scrollButtonActiveStateChange.next({ buttonType, active });
2483
+ }
2484
+ isDisabled = (buttonType) => this.owner[`${buttonType}ScrollButton`]?.nativeElement.classList.contains('k-disabled');
2485
+ 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 });
2486
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestionsScrollService });
2487
+ }
2488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestionsScrollService, decorators: [{
2489
+ type: Injectable
2490
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i1.LocalizationService }] });
2491
+
2256
2492
  /* eslint-disable @typescript-eslint/no-explicit-any */
2257
2493
  /* eslint-disable @typescript-eslint/no-empty-function */
2258
2494
  /**
2259
2495
  * @hidden
2260
2496
  */
2261
2497
  class SuggestedActionsComponent extends ChatItem {
2498
+ chatService;
2499
+ localization;
2500
+ scrollService;
2501
+ ngZone;
2502
+ renderer;
2503
+ get defaultClass() {
2504
+ if (this.type === 'suggestion') {
2505
+ return this.chatService.suggestionsLayout === 'wrap' || this.chatService.suggestionsLayout === 'scroll';
2506
+ }
2507
+ else if (this.type === 'action') {
2508
+ return this.chatService.quickActionsLayout === 'wrap' || this.chatService.quickActionsLayout === 'scroll';
2509
+ }
2510
+ }
2511
+ get scrollableClass() {
2512
+ if (this.type === 'suggestion') {
2513
+ return this.chatService.suggestionsLayout === 'scroll';
2514
+ }
2515
+ else if (this.type === 'action') {
2516
+ return this.chatService.quickActionsLayout === 'scroll';
2517
+ }
2518
+ }
2519
+ get scrollButtonsClass() {
2520
+ if (this.type === 'suggestion') {
2521
+ return this.chatService.suggestionsLayout === 'scrollbuttons';
2522
+ }
2523
+ else if (this.type === 'action') {
2524
+ return this.chatService.quickActionsLayout === 'scrollbuttons';
2525
+ }
2526
+ }
2527
+ get role() {
2528
+ if (!this.hasScrollButtons) {
2529
+ return 'group';
2530
+ }
2531
+ return null;
2532
+ }
2262
2533
  actions;
2263
2534
  suggestions;
2264
2535
  tabbable;
2536
+ type;
2265
2537
  suggestionTemplate;
2266
2538
  dispatchAction = new EventEmitter();
2267
2539
  dispatchSuggestion = new EventEmitter();
2268
- defaultClass = true;
2269
- role = 'group';
2270
2540
  items;
2541
+ suggestionsContainer;
2542
+ prevScrollButton;
2543
+ nextScrollButton;
2271
2544
  selectedIndex = 0;
2272
2545
  activeIndex = -1;
2273
2546
  active = false;
2547
+ get hasScrollButtons() {
2548
+ return this.type === 'suggestion' ? this.chatService.suggestionsLayout === 'scrollbuttons' : this.chatService.quickActionsLayout === 'scrollbuttons';
2549
+ }
2550
+ subscriptions = new Subscription();
2551
+ resizeObserver = null;
2274
2552
  actionKeyHandlers = {
2275
2553
  [Keys.Tab]: (e) => this.changeSelectedIndex(e),
2276
2554
  [Keys.Enter]: (_, action) => this.actionClick(action),
@@ -2281,6 +2559,58 @@ class SuggestedActionsComponent extends ChatItem {
2281
2559
  [Keys.Enter]: (_, suggestion) => this.suggestionClick(suggestion),
2282
2560
  [Keys.Space]: (_, suggestion) => this.suggestionClick(suggestion),
2283
2561
  };
2562
+ constructor(chatService, localization, scrollService, ngZone, renderer) {
2563
+ super();
2564
+ this.chatService = chatService;
2565
+ this.localization = localization;
2566
+ this.scrollService = scrollService;
2567
+ this.ngZone = ngZone;
2568
+ this.renderer = renderer;
2569
+ this.scrollService.owner = this;
2570
+ }
2571
+ ngAfterViewInit() {
2572
+ const layoutChangeObservable = this.type === 'suggestion'
2573
+ ? this.chatService.suggestionsLayoutChange$
2574
+ : this.chatService.quickActionsLayoutChange$;
2575
+ this.subscriptions.add(layoutChangeObservable.subscribe((layoutMode) => {
2576
+ if (layoutMode === 'scrollbuttons') {
2577
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
2578
+ if (this.suggestionsContainer) {
2579
+ this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement);
2580
+ }
2581
+ this.scrollService.toggleScrollButtonsState();
2582
+ });
2583
+ }
2584
+ }));
2585
+ this.subscriptions.add(this.scrollService.scrollButtonActiveStateChange.subscribe((change) => {
2586
+ this.toggleScrollButtonState(change.buttonType, change.active);
2587
+ }));
2588
+ if (this.hasScrollButtons && this.suggestionsContainer) {
2589
+ this.ngZone.runOutsideAngular(() => {
2590
+ this.resizeObserver = new ResizeObserver(() => {
2591
+ this.ngZone.run(() => {
2592
+ this.scrollService.toggleScrollButtonsState();
2593
+ });
2594
+ });
2595
+ this.resizeObserver.observe(this.suggestionsContainer.nativeElement);
2596
+ });
2597
+ }
2598
+ if (this.hasScrollButtons) {
2599
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
2600
+ this.scrollService.toggleScrollButtonsState();
2601
+ });
2602
+ if (this.suggestionsContainer) {
2603
+ this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement);
2604
+ }
2605
+ }
2606
+ }
2607
+ ngOnDestroy() {
2608
+ this.subscriptions.unsubscribe();
2609
+ if (this.resizeObserver) {
2610
+ this.resizeObserver.disconnect();
2611
+ this.resizeObserver = null;
2612
+ }
2613
+ }
2284
2614
  isSelected(index) {
2285
2615
  return this.selected && this.selectedIndex === index;
2286
2616
  }
@@ -2314,67 +2644,130 @@ class SuggestedActionsComponent extends ChatItem {
2314
2644
  handler(e, suggestion);
2315
2645
  }
2316
2646
  }
2647
+ getScrollButtonTitle(direction) {
2648
+ let currentButton;
2649
+ if (this.localization.rtl) {
2650
+ currentButton = direction === 'prev' ? 'nextSuggestionsButtonTitle' : 'previousSuggestionsButtonTitle';
2651
+ }
2652
+ else {
2653
+ currentButton = direction === 'prev' ? 'previousSuggestionsButtonTitle' : 'nextSuggestionsButtonTitle';
2654
+ }
2655
+ return this.localization.get(currentButton);
2656
+ }
2657
+ onScroll(event) {
2658
+ this.scrollService.onScroll(event);
2659
+ }
2660
+ scrollSuggestions(direction) {
2661
+ this.scrollService.scrollSuggestions(direction);
2662
+ }
2317
2663
  focus() { }
2664
+ toggleScrollButtonState(buttonType, active) {
2665
+ const button = this[`${buttonType}ScrollButton`];
2666
+ if (button?.nativeElement) {
2667
+ if (active) {
2668
+ this.renderer.removeClass(button.nativeElement, 'k-disabled');
2669
+ }
2670
+ else {
2671
+ this.renderer.addClass(button.nativeElement, 'k-disabled');
2672
+ }
2673
+ }
2674
+ }
2318
2675
  changeSelectedIndex(e) {
2319
2676
  const offset = e.shiftKey ? -1 : 1;
2320
2677
  const prevIndex = this.selectedIndex;
2321
2678
  this.selectedIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1));
2322
2679
  }
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: [{
2680
+ 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 });
2681
+ 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
2682
  provide: ChatItem,
2326
2683
  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>
2684
+ },
2685
+ SuggestionsScrollService
2686
+ ], 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: `
2687
+ <span
2688
+ *ngIf="hasScrollButtons"
2689
+ #prevScrollButton
2690
+ kendoChatScrollableButton
2691
+ [prev]="true"
2692
+ [title]="getScrollButtonTitle('prev')"
2693
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2694
+ (onClick)="scrollSuggestions($event)"
2695
+ >
2696
+ </span>
2697
+ <div class="k-suggestion-group" *ngIf="hasScrollButtons; else noScrollButtons"
2698
+ #suggestionsContainer
2699
+ role="group"
2700
+ (scroll)="onScroll($event)">
2701
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2702
+ </div>
2703
+
2704
+ <ng-template #noScrollButtons>
2705
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2706
+ </ng-template>
2346
2707
 
2347
- <ng-container *ngIf="suggestions">
2348
- <ng-container *ngIf="!suggestionTemplate?.templateRef">
2708
+ <ng-template #suggestionsContent>
2709
+ <ng-container *ngIf="actions">
2349
2710
  <span
2350
2711
  #item
2351
- *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2352
- class="k-suggestion"
2712
+ *ngFor="let action of actions; index as index; first as first; last as last"
2713
+ class="k-suggestion k-suggestion-primary"
2353
2714
  role="button"
2354
2715
  [class.k-selected]="isSelected(index)"
2355
2716
  [class.k-focus]="isSelected(index)"
2356
2717
  [class.k-active]="isActive(index)"
2357
2718
  [attr.tabindex]="0"
2358
- (click)="suggestionClick(suggestion, index)"
2359
- (keydown)="suggestionKeydown($event, suggestion)"
2719
+ (click)="actionClick(action, index)"
2720
+ (keydown)="actionKeydown($event, action)"
2360
2721
  (mousedown)="toggleActiveState(true, index)"
2361
2722
  (mouseup)="toggleActiveState(false, index)"
2362
2723
  >
2363
- {{ suggestion.text }}
2724
+ {{ action.title || action.value }}
2364
2725
  </span>
2365
2726
  </ng-container>
2366
2727
 
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 }"
2728
+ <ng-container *ngIf="suggestions">
2729
+ <ng-container *ngIf="!suggestionTemplate?.templateRef">
2730
+ <span
2731
+ #item
2732
+ *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2733
+ class="k-suggestion"
2734
+ role="button"
2735
+ [class.k-selected]="isSelected(index)"
2736
+ [class.k-focus]="isSelected(index)"
2737
+ [class.k-active]="isActive(index)"
2738
+ [attr.tabindex]="0"
2739
+ (click)="suggestionClick(suggestion, index)"
2740
+ (keydown)="suggestionKeydown($event, suggestion)"
2741
+ (mousedown)="toggleActiveState(true, index)"
2742
+ (mouseup)="toggleActiveState(false, index)"
2372
2743
  >
2373
- </ng-template>
2744
+ {{ suggestion.text }}
2745
+ </span>
2746
+ </ng-container>
2747
+
2748
+ <ng-container *ngIf="suggestionTemplate?.templateRef">
2749
+ <ng-container *ngFor="let suggestion of suggestions">
2750
+ <ng-template
2751
+ [ngTemplateOutlet]="suggestionTemplate.templateRef"
2752
+ [ngTemplateOutletContext]="{ $implicit: suggestion }"
2753
+ >
2754
+ </ng-template>
2755
+ </ng-container>
2374
2756
  </ng-container>
2375
2757
  </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"] }] });
2758
+ </ng-template>
2759
+
2760
+ <span
2761
+ *ngIf="hasScrollButtons"
2762
+ #nextScrollButton
2763
+ kendoChatScrollableButton
2764
+ [prev]="false"
2765
+ [title]="getScrollButtonTitle('next')"
2766
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2767
+ (onClick)="scrollSuggestions($event)"
2768
+ >
2769
+ </span>
2770
+ `, 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
2771
  }
2379
2772
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, decorators: [{
2380
2773
  type: Component,
@@ -2383,82 +2776,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2383
2776
  providers: [{
2384
2777
  provide: ChatItem,
2385
2778
  useExisting: forwardRef(() => SuggestedActionsComponent)
2386
- }],
2779
+ },
2780
+ SuggestionsScrollService],
2387
2781
  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>
2782
+ <span
2783
+ *ngIf="hasScrollButtons"
2784
+ #prevScrollButton
2785
+ kendoChatScrollableButton
2786
+ [prev]="true"
2787
+ [title]="getScrollButtonTitle('prev')"
2788
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2789
+ (onClick)="scrollSuggestions($event)"
2790
+ >
2791
+ </span>
2792
+ <div class="k-suggestion-group" *ngIf="hasScrollButtons; else noScrollButtons"
2793
+ #suggestionsContainer
2794
+ role="group"
2795
+ (scroll)="onScroll($event)">
2796
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2797
+ </div>
2406
2798
 
2407
- <ng-container *ngIf="suggestions">
2408
- <ng-container *ngIf="!suggestionTemplate?.templateRef">
2799
+ <ng-template #noScrollButtons>
2800
+ <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
2801
+ </ng-template>
2802
+
2803
+ <ng-template #suggestionsContent>
2804
+ <ng-container *ngIf="actions">
2409
2805
  <span
2410
2806
  #item
2411
- *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2412
- class="k-suggestion"
2807
+ *ngFor="let action of actions; index as index; first as first; last as last"
2808
+ class="k-suggestion k-suggestion-primary"
2413
2809
  role="button"
2414
2810
  [class.k-selected]="isSelected(index)"
2415
2811
  [class.k-focus]="isSelected(index)"
2416
2812
  [class.k-active]="isActive(index)"
2417
2813
  [attr.tabindex]="0"
2418
- (click)="suggestionClick(suggestion, index)"
2419
- (keydown)="suggestionKeydown($event, suggestion)"
2814
+ (click)="actionClick(action, index)"
2815
+ (keydown)="actionKeydown($event, action)"
2420
2816
  (mousedown)="toggleActiveState(true, index)"
2421
2817
  (mouseup)="toggleActiveState(false, index)"
2422
2818
  >
2423
- {{ suggestion.text }}
2819
+ {{ action.title || action.value }}
2424
2820
  </span>
2425
2821
  </ng-container>
2426
2822
 
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 }"
2823
+ <ng-container *ngIf="suggestions">
2824
+ <ng-container *ngIf="!suggestionTemplate?.templateRef">
2825
+ <span
2826
+ #item
2827
+ *ngFor="let suggestion of suggestions; index as index; first as first; last as last"
2828
+ class="k-suggestion"
2829
+ role="button"
2830
+ [class.k-selected]="isSelected(index)"
2831
+ [class.k-focus]="isSelected(index)"
2832
+ [class.k-active]="isActive(index)"
2833
+ [attr.tabindex]="0"
2834
+ (click)="suggestionClick(suggestion, index)"
2835
+ (keydown)="suggestionKeydown($event, suggestion)"
2836
+ (mousedown)="toggleActiveState(true, index)"
2837
+ (mouseup)="toggleActiveState(false, index)"
2432
2838
  >
2433
- </ng-template>
2839
+ {{ suggestion.text }}
2840
+ </span>
2841
+ </ng-container>
2842
+
2843
+ <ng-container *ngIf="suggestionTemplate?.templateRef">
2844
+ <ng-container *ngFor="let suggestion of suggestions">
2845
+ <ng-template
2846
+ [ngTemplateOutlet]="suggestionTemplate.templateRef"
2847
+ [ngTemplateOutletContext]="{ $implicit: suggestion }"
2848
+ >
2849
+ </ng-template>
2850
+ </ng-container>
2434
2851
  </ng-container>
2435
2852
  </ng-container>
2436
- </ng-container>
2853
+ </ng-template>
2854
+
2855
+ <span
2856
+ *ngIf="hasScrollButtons"
2857
+ #nextScrollButton
2858
+ kendoChatScrollableButton
2859
+ [prev]="false"
2860
+ [title]="getScrollButtonTitle('next')"
2861
+ class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button"
2862
+ (onClick)="scrollSuggestions($event)"
2863
+ >
2864
+ </span>
2437
2865
  `,
2438
2866
  standalone: true,
2439
- imports: [NgFor, NgIf, NgTemplateOutlet]
2867
+ imports: [NgFor, NgIf, NgTemplateOutlet, ChatScrollableButtonComponent]
2440
2868
  }]
2441
- }], propDecorators: { actions: [{
2869
+ }], ctorParameters: () => [{ type: ChatService }, { type: i1.LocalizationService }, { type: SuggestionsScrollService }, { type: i0.NgZone }, { type: i0.Renderer2 }], propDecorators: { defaultClass: [{
2870
+ type: HostBinding,
2871
+ args: ['class.k-suggestion-group']
2872
+ }], scrollableClass: [{
2873
+ type: HostBinding,
2874
+ args: ['class.k-suggestion-group-scrollable']
2875
+ }], scrollButtonsClass: [{
2876
+ type: HostBinding,
2877
+ args: ['class.k-suggestion-scrollwrap']
2878
+ }], role: [{
2879
+ type: HostBinding,
2880
+ args: ['attr.role']
2881
+ }], actions: [{
2442
2882
  type: Input
2443
2883
  }], suggestions: [{
2444
2884
  type: Input
2445
2885
  }], tabbable: [{
2446
2886
  type: Input
2887
+ }], type: [{
2888
+ type: Input
2447
2889
  }], suggestionTemplate: [{
2448
2890
  type: Input
2449
2891
  }], dispatchAction: [{
2450
2892
  type: Output
2451
2893
  }], dispatchSuggestion: [{
2452
2894
  type: Output
2453
- }], defaultClass: [{
2454
- type: HostBinding,
2455
- args: ['class.k-suggestion-group']
2456
- }], role: [{
2457
- type: HostBinding,
2458
- args: ['attr.role']
2459
2895
  }], items: [{
2460
2896
  type: ViewChildren,
2461
2897
  args: ['item']
2898
+ }], suggestionsContainer: [{
2899
+ type: ViewChild,
2900
+ args: ['suggestionsContainer', { read: ElementRef, static: false }]
2901
+ }], prevScrollButton: [{
2902
+ type: ViewChild,
2903
+ args: ['prevScrollButton', { read: ElementRef }]
2904
+ }], nextScrollButton: [{
2905
+ type: ViewChild,
2906
+ args: ['nextScrollButton', { read: ElementRef }]
2462
2907
  }] } });
2463
2908
 
2464
2909
  /* eslint-disable @typescript-eslint/no-empty-function */
@@ -2499,7 +2944,7 @@ class ChatFileComponent extends ChatItem {
2499
2944
  useExisting: forwardRef(() => ChatFileComponent)
2500
2945
  }], usesInheritance: true, ngImport: i0, template: `
2501
2946
  <kendo-icon-wrapper
2502
- size="xxlarge"
2947
+ size="xlarge"
2503
2948
  [name]="fileGroupClass(chatFile.extension)"
2504
2949
  [svgIcon]="fileThumbnail(chatFile.extension)"
2505
2950
  >
@@ -2540,7 +2985,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2540
2985
  }],
2541
2986
  template: `
2542
2987
  <kendo-icon-wrapper
2543
- size="xxlarge"
2988
+ size="xlarge"
2544
2989
  [name]="fileGroupClass(chatFile.extension)"
2545
2990
  [svgIcon]="fileThumbnail(chatFile.extension)"
2546
2991
  >
@@ -2790,6 +3235,7 @@ class MessageBoxComponent {
2790
3235
  return;
2791
3236
  }
2792
3237
  const message = {
3238
+ id: guid(),
2793
3239
  text: this.inputValue,
2794
3240
  timestamp: new Date(),
2795
3241
  author: { id: this.authorId },
@@ -2927,6 +3373,7 @@ class MessageBoxComponent {
2927
3373
  *ngIf="suggestions?.length > 0"
2928
3374
  #suggestedActions
2929
3375
  [suggestions]="suggestions"
3376
+ type="suggestion"
2930
3377
  [suggestionTemplate]="suggestionTemplate"
2931
3378
  [tabbable]="true"
2932
3379
  (dispatchSuggestion)="dispatchSuggestion($event)"
@@ -3027,7 +3474,7 @@ class MessageBoxComponent {
3027
3474
  [showFileList]="false"
3028
3475
  (select)="handleFileSelect($event)"
3029
3476
  ></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"] }] });
3477
+ `, 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
3478
  }
3032
3479
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBoxComponent, decorators: [{
3033
3480
  type: Component,
@@ -3038,6 +3485,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3038
3485
  *ngIf="suggestions?.length > 0"
3039
3486
  #suggestedActions
3040
3487
  [suggestions]="suggestions"
3488
+ type="suggestion"
3041
3489
  [suggestionTemplate]="suggestionTemplate"
3042
3490
  [tabbable]="true"
3043
3491
  (dispatchSuggestion)="dispatchSuggestion($event)"
@@ -3183,6 +3631,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3183
3631
  type: Output
3184
3632
  }] } });
3185
3633
 
3634
+ /**
3635
+ * Defines a template for displaying custom content inside the Chat messages.
3636
+ *
3637
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageContentTemplate` directive inside the `<kendo-chat>` component.
3638
+ * The template context is set to the `message` instance.
3639
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3640
+ *
3641
+ * @example
3642
+ * ```html
3643
+ * <kendo-chat>
3644
+ * <ng-template kendoChatMessageContentTemplate let-message>
3645
+ * <div>Message: {{ message.text }}</div>
3646
+ * </ng-template>
3647
+ * </kendo-chat>
3648
+ * ```
3649
+ */
3650
+ class MessageContentTemplateDirective {
3651
+ templateRef;
3652
+ constructor(templateRef) {
3653
+ this.templateRef = templateRef;
3654
+ }
3655
+ 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 });
3656
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatMessageContentTemplate]", ngImport: i0 });
3657
+ }
3658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageContentTemplateDirective, decorators: [{
3659
+ type: Directive,
3660
+ args: [{
3661
+ selector: '[kendoChatMessageContentTemplate]',
3662
+ standalone: true
3663
+ }]
3664
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3665
+ type: Optional
3666
+ }] }] });
3667
+
3186
3668
  /**
3187
3669
  * Defines a template for displaying the chat timestamp.
3188
3670
  *
@@ -3248,6 +3730,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3248
3730
  }]
3249
3731
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
3250
3732
 
3733
+ /**
3734
+ * Defines a template for displaying fully custom Chat message bubbles.
3735
+ *
3736
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageTemplate` directive inside the `<kendo-chat>` component.
3737
+ * The template context is set to the `message` instance.
3738
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3739
+ *
3740
+ * @example
3741
+ * ```html
3742
+ * <kendo-chat>
3743
+ * <ng-template kendoChatMessageTemplate let-message>
3744
+ * <div>Message: {{ message.text }}</div>
3745
+ * </ng-template>
3746
+ * </kendo-chat>
3747
+ * ```
3748
+ */
3749
+ class MessageTemplateDirective {
3750
+ templateRef;
3751
+ constructor(templateRef) {
3752
+ this.templateRef = templateRef;
3753
+ }
3754
+ 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 });
3755
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageTemplateDirective, isStandalone: true, selector: "[kendoChatMessageTemplate]", ngImport: i0 });
3756
+ }
3757
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageTemplateDirective, decorators: [{
3758
+ type: Directive,
3759
+ args: [{
3760
+ selector: '[kendoChatMessageTemplate]',
3761
+ standalone: true
3762
+ }]
3763
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3764
+ type: Optional
3765
+ }] }] });
3766
+
3767
+ /**
3768
+ * Defines a template for displaying custom content inside the current user's messages in the Chat.
3769
+ *
3770
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatAuthorMessageContentTemplate` directive inside the `<kendo-chat>` component.
3771
+ * The template context is set to the `message` instance.
3772
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3773
+ *
3774
+ * @example
3775
+ * ```html
3776
+ * <kendo-chat>
3777
+ * <ng-template kendoChatAuthorMessageContentTemplate let-message>
3778
+ * <div>Message: {{ message.text }}</div>
3779
+ * </ng-template>
3780
+ * </kendo-chat>
3781
+ * ```
3782
+ */
3783
+ class AuthorMessageContentTemplateDirective {
3784
+ templateRef;
3785
+ constructor(templateRef) {
3786
+ this.templateRef = templateRef;
3787
+ }
3788
+ 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 });
3789
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AuthorMessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatAuthorMessageContentTemplate]", ngImport: i0 });
3790
+ }
3791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageContentTemplateDirective, decorators: [{
3792
+ type: Directive,
3793
+ args: [{
3794
+ selector: '[kendoChatAuthorMessageContentTemplate]',
3795
+ standalone: true
3796
+ }]
3797
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3798
+ type: Optional
3799
+ }] }] });
3800
+
3801
+ /**
3802
+ * Defines a template for displaying custom content inside the other users' messages in the Chat.
3803
+ *
3804
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatReceiverMessageContentTemplate` directive inside the `<kendo-chat>` component.
3805
+ * The template context is set to the `message` instance.
3806
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3807
+ *
3808
+ * @example
3809
+ * ```html
3810
+ * <kendo-chat>
3811
+ * <ng-template kendoChatReceiverMessageContentTemplate let-message>
3812
+ * <div>Message: {{ message.text }}</div>
3813
+ * </ng-template>
3814
+ * </kendo-chat>
3815
+ * ```
3816
+ */
3817
+ class ReceiverMessageContentTemplateDirective {
3818
+ templateRef;
3819
+ constructor(templateRef) {
3820
+ this.templateRef = templateRef;
3821
+ }
3822
+ 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 });
3823
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ReceiverMessageContentTemplateDirective, isStandalone: true, selector: "[kendoChatReceiverMessageContentTemplate]", ngImport: i0 });
3824
+ }
3825
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageContentTemplateDirective, decorators: [{
3826
+ type: Directive,
3827
+ args: [{
3828
+ selector: '[kendoChatReceiverMessageContentTemplate]',
3829
+ standalone: true
3830
+ }]
3831
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3832
+ type: Optional
3833
+ }] }] });
3834
+
3835
+ /**
3836
+ * Defines a template for displaying fully custom Chat message bubbles for the other users.
3837
+ *
3838
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatReceiverMessageTemplate` directive inside the `<kendo-chat>` component.
3839
+ * The template context is set to the `message` instance.
3840
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3841
+ *
3842
+ * @example
3843
+ * ```html
3844
+ * <kendo-chat>
3845
+ * <ng-template kendoChatReceiverMessageTemplate let-message>
3846
+ * <div>Message: {{ message.text }}</div>
3847
+ * </ng-template>
3848
+ * </kendo-chat>
3849
+ * ```
3850
+ */
3851
+ class ReceiverMessageTemplateDirective {
3852
+ templateRef;
3853
+ constructor(templateRef) {
3854
+ this.templateRef = templateRef;
3855
+ }
3856
+ 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 });
3857
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ReceiverMessageTemplateDirective, isStandalone: true, selector: "[kendoChatReceiverMessageTemplate]", ngImport: i0 });
3858
+ }
3859
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReceiverMessageTemplateDirective, decorators: [{
3860
+ type: Directive,
3861
+ args: [{
3862
+ selector: '[kendoChatReceiverMessageTemplate]',
3863
+ standalone: true
3864
+ }]
3865
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3866
+ type: Optional
3867
+ }] }] });
3868
+
3869
+ /**
3870
+ * Defines a template for displaying fully custom Chat message bubbles for the current user.
3871
+ *
3872
+ * To define a message template, nest an `<ng-template>` tag with the `kendoChatAuthorMessageTemplate` directive inside the `<kendo-chat>` component.
3873
+ * The template context is set to the `message` instance.
3874
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
3875
+ *
3876
+ * @example
3877
+ * ```html
3878
+ * <kendo-chat>
3879
+ * <ng-template kendoChatAuthorMessageTemplate let-message>
3880
+ * <div>Message: {{ message.text }}</div>
3881
+ * </ng-template>
3882
+ * </kendo-chat>
3883
+ * ```
3884
+ */
3885
+ class AuthorMessageTemplateDirective {
3886
+ templateRef;
3887
+ constructor(templateRef) {
3888
+ this.templateRef = templateRef;
3889
+ }
3890
+ 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 });
3891
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: AuthorMessageTemplateDirective, isStandalone: true, selector: "[kendoChatAuthorMessageTemplate]", ngImport: i0 });
3892
+ }
3893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthorMessageTemplateDirective, decorators: [{
3894
+ type: Directive,
3895
+ args: [{
3896
+ selector: '[kendoChatAuthorMessageTemplate]',
3897
+ standalone: true
3898
+ }]
3899
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
3900
+ type: Optional
3901
+ }] }] });
3902
+
3251
3903
  // eslint-disable no-forward-ref
3252
3904
  /**
3253
3905
  * @hidden
@@ -3257,13 +3909,23 @@ class MessageComponent extends ChatItem {
3257
3909
  intl;
3258
3910
  chatService;
3259
3911
  localization;
3260
- message;
3912
+ cdr;
3913
+ set message(value) {
3914
+ this._message = value;
3915
+ }
3916
+ get message() {
3917
+ return this._message;
3918
+ }
3261
3919
  tabbable;
3262
- template;
3920
+ authorMessageContentTemplate;
3921
+ receiverMessageContentTemplate;
3922
+ messageContentTemplate;
3923
+ authorMessageTemplate;
3924
+ receiverMessageTemplate;
3925
+ messageTemplate;
3263
3926
  statusTemplate;
3264
3927
  showMessageTime = true;
3265
3928
  authorId;
3266
- contextMenuVisibilityChange = new EventEmitter();
3267
3929
  cssClass = true;
3268
3930
  get removedClass() {
3269
3931
  return this.message.isDeleted;
@@ -3278,44 +3940,142 @@ class MessageComponent extends ChatItem {
3278
3940
  this.onExpandableKeydown(event);
3279
3941
  }
3280
3942
  selected;
3281
- active = false;
3282
- selectOnMenuClose = false;
3283
3943
  get tabIndex() {
3284
3944
  return this.tabbable ? '0' : '-1';
3285
3945
  }
3286
- onContextMenu(event) {
3287
- if (this.message.isDeleted) {
3288
- return;
3289
- }
3290
- this.active = true;
3291
- event.stopPropagation();
3292
- }
3293
3946
  expandIcon = chevronDownIcon;
3294
3947
  collapseIcon = chevronUpIcon;
3295
3948
  downloadIcon = downloadIcon;
3296
3949
  isMessageExpanded = false;
3297
- contextMenuActions = [];
3950
+ showExpandCollapseIcon = false;
3298
3951
  fileActions = [];
3952
+ toolbarActions = [];
3299
3953
  parts = [];
3300
- constructor(element, intl, chatService, localization) {
3954
+ get useCustomBubbleTemplate() {
3955
+ return !!(this.getActiveBubbleTemplate());
3956
+ }
3957
+ get useCustomContentTemplate() {
3958
+ return !!(this.getActiveContentTemplate());
3959
+ }
3960
+ get hasMessageContent() {
3961
+ return !!(this.message?.text || this.message?.files?.length > 0);
3962
+ }
3963
+ get hasFiles() {
3964
+ return this.message?.files?.length > 0;
3965
+ }
3966
+ get hasMultipleFiles() {
3967
+ return this.message?.files?.length > 1;
3968
+ }
3969
+ get isActiveMessage() {
3970
+ return this.chatService.active && this.message?.id === this.chatService.activeMessage?.id;
3971
+ }
3972
+ get isMessageExpandable() {
3973
+ const isOwn = this.isOwnMessage(this.message);
3974
+ const messageSettings = isOwn
3975
+ ? this.chatService.authorMessageSettings
3976
+ : this.chatService.receiverMessageSettings;
3977
+ if (isPresent(messageSettings?.allowMessageCollapse)) {
3978
+ return messageSettings.allowMessageCollapse;
3979
+ }
3980
+ return this.chatService.allowMessageCollapse || false;
3981
+ }
3982
+ get showToolbar() {
3983
+ if (this.message?.isDeleted) {
3984
+ return false;
3985
+ }
3986
+ const hasComponentActions = this.chatService.messageToolbarActions?.length > 0;
3987
+ const hasMessageActions = this.toolbarActions?.length > 0;
3988
+ return hasComponentActions || hasMessageActions;
3989
+ }
3990
+ subs = new Subscription();
3991
+ _message;
3992
+ constructor(element, intl, chatService, localization, cdr) {
3301
3993
  super();
3302
3994
  this.element = element;
3303
3995
  this.intl = intl;
3304
3996
  this.chatService = chatService;
3305
3997
  this.localization = localization;
3998
+ this.cdr = cdr;
3306
3999
  }
3307
4000
  ngOnInit() {
3308
- this.contextMenuActions = this.transformActions(this.chatService.messageContextMenuActions);
3309
- this.fileActions = this.transformActions(this.chatService.fileActions);
4001
+ this.fileActions = this.getFileActions();
4002
+ this.toolbarActions = this.getToolbarActions();
4003
+ const settingsChange$ = this.isOwnMessage(this.message)
4004
+ ? this.chatService.authorMessageSettingsChange$
4005
+ : this.chatService.receiverMessageSettingsChange$;
4006
+ this.subs.add(settingsChange$.subscribe(() => {
4007
+ this.fileActions = this.getFileActions();
4008
+ this.toolbarActions = this.getToolbarActions();
4009
+ setTimeout(() => {
4010
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4011
+ });
4012
+ }));
4013
+ this.subs.add(this.chatService.allowMessageCollapseChange$.subscribe(() => {
4014
+ setTimeout(() => {
4015
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4016
+ });
4017
+ }));
3310
4018
  if (this.message.id) {
3311
4019
  this.chatService.registerMessageElement(this.message.id, this.element);
3312
4020
  }
3313
4021
  this.parts = this.getFormattedTextParts(this.message.text);
3314
4022
  }
4023
+ ngAfterViewInit() {
4024
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
4025
+ this.cdr.detectChanges();
4026
+ }
3315
4027
  ngOnDestroy() {
3316
4028
  if (this.message.id) {
3317
4029
  this.chatService.unregisterMessageElement(this.message.id);
3318
4030
  }
4031
+ this.subs.unsubscribe();
4032
+ }
4033
+ calculateExpandCollapseIconVisibility() {
4034
+ if (this.isMessageExpanded) {
4035
+ return true;
4036
+ }
4037
+ const bubbleContent = this.element.nativeElement.querySelector('.k-bubble-content');
4038
+ if (!bubbleContent) {
4039
+ return false;
4040
+ }
4041
+ const hasVerticalOverflow = bubbleContent.scrollHeight > bubbleContent.clientHeight;
4042
+ const hasHorizontalOverflow = bubbleContent.scrollWidth > bubbleContent.clientWidth;
4043
+ if (this.useCustomContentTemplate) {
4044
+ return hasVerticalOverflow || hasHorizontalOverflow;
4045
+ }
4046
+ const messageText = this.element.nativeElement.querySelector('.k-chat-bubble-text');
4047
+ const hasTextOverflow = messageText?.scrollWidth > messageText?.clientWidth;
4048
+ return hasTextOverflow || hasVerticalOverflow || hasHorizontalOverflow;
4049
+ }
4050
+ getActiveBubbleTemplate() {
4051
+ const isOwn = this.isOwnMessage(this.message);
4052
+ if (isOwn && this.authorMessageTemplate) {
4053
+ return this.authorMessageTemplate;
4054
+ }
4055
+ if (!isOwn && this.receiverMessageTemplate) {
4056
+ return this.receiverMessageTemplate;
4057
+ }
4058
+ if (this.messageTemplate) {
4059
+ return this.messageTemplate;
4060
+ }
4061
+ return null;
4062
+ }
4063
+ getActiveContentTemplate() {
4064
+ const isOwn = this.isOwnMessage(this.message);
4065
+ if (isOwn && this.authorMessageContentTemplate) {
4066
+ return this.authorMessageContentTemplate;
4067
+ }
4068
+ if (!isOwn && this.receiverMessageContentTemplate) {
4069
+ return this.receiverMessageContentTemplate;
4070
+ }
4071
+ if (this.messageContentTemplate) {
4072
+ return this.messageContentTemplate;
4073
+ }
4074
+ return null;
4075
+ }
4076
+ getDeletedMessageText() {
4077
+ const isOwn = this.isOwnMessage(this.message);
4078
+ return isOwn ? this.textFor('deletedMessageSenderText') : this.textFor('deletedMessageReceiverText');
3319
4079
  }
3320
4080
  textFor(key) {
3321
4081
  return this.localization.get(key);
@@ -3343,66 +4103,19 @@ class MessageComponent extends ChatItem {
3343
4103
  this.toggleMessageState(event);
3344
4104
  }
3345
4105
  }
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
4106
  onToolbarAction(event, action, message) {
3353
4107
  event.stopImmediatePropagation();
3354
4108
  this.chatService.emit('toolbarAction', { action, message });
3355
4109
  }
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
4110
  onFileAction(action, file) {
3366
4111
  if (action.originalAction.id === 'download') {
3367
4112
  this.chatService.emit('fileDownload', { files: [file], message: this.message });
3368
4113
  }
3369
4114
  this.chatService.emit('fileAction', { action: action.originalAction, file });
3370
4115
  }
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
4116
  getMessageById(id) {
3381
4117
  return this.chatService.getMessageById(id);
3382
4118
  }
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
4119
  onReplyReferenceClick(event, replyToId) {
3407
4120
  event.stopPropagation();
3408
4121
  this.chatService.emit('replyReferenceClick', replyToId);
@@ -3414,9 +4127,9 @@ class MessageComponent extends ChatItem {
3414
4127
  this.onActionButtonClick(originalEvent);
3415
4128
  }
3416
4129
  }
3417
- this.active = false;
3418
- this.contextMenuVisibilityChange.emit(false);
3419
- if (this.selectOnMenuClose) {
4130
+ this.chatService.active = false;
4131
+ this.chatService.emit('contextMenuVisibilityChange', false);
4132
+ if (this.chatService.selectOnMenuClose) {
3420
4133
  this.selected = true;
3421
4134
  this.focus();
3422
4135
  }
@@ -3425,16 +4138,16 @@ class MessageComponent extends ChatItem {
3425
4138
  const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
3426
4139
  const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
3427
4140
  if (clickOutsideMessage && !menuItemClick) {
3428
- this.selectOnMenuClose = false;
4141
+ this.chatService.selectOnMenuClose = false;
3429
4142
  }
3430
4143
  }
3431
4144
  handleMenuOpen() {
3432
- this.selectOnMenuClose = this.selected;
3433
- this.contextMenuVisibilityChange.emit(true);
4145
+ this.chatService.selectOnMenuClose = this.selected;
4146
+ this.chatService.emit('contextMenuVisibilityChange', true);
3434
4147
  }
3435
4148
  onActionPopupChange(expanded) {
3436
4149
  if (expanded) {
3437
- this.active = true;
4150
+ this.chatService.active = true;
3438
4151
  this.handleMenuOpen();
3439
4152
  }
3440
4153
  else {
@@ -3444,124 +4157,211 @@ class MessageComponent extends ChatItem {
3444
4157
  isOwnMessage(msg) {
3445
4158
  return isAuthor(this.authorId, msg);
3446
4159
  }
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: [
4160
+ getFormattedTextParts(text) {
4161
+ if (!text) {
4162
+ return [];
4163
+ }
4164
+ const parts = [];
4165
+ const urlMatches = Array.from(text.matchAll(URL_REGEX));
4166
+ let lastIndex = 0;
4167
+ for (const match of urlMatches) {
4168
+ const url = match[1];
4169
+ const matchStart = match.index;
4170
+ if (!isPresent(matchStart)) {
4171
+ continue;
4172
+ }
4173
+ if (matchStart > lastIndex) {
4174
+ parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
4175
+ }
4176
+ parts.push({ type: 'link', content: url, href: url });
4177
+ lastIndex = matchStart + match[0].length;
4178
+ }
4179
+ if (lastIndex < text.length) {
4180
+ parts.push({ type: 'text', content: text.substring(lastIndex) });
4181
+ }
4182
+ return parts;
4183
+ }
4184
+ getMessageSettings() {
4185
+ return this.isOwnMessage(this.message)
4186
+ ? this.chatService.authorMessageSettings
4187
+ : this.chatService.receiverMessageSettings;
4188
+ }
4189
+ getToolbarActions() {
4190
+ const messageSettings = this.getMessageSettings();
4191
+ return messageSettings?.messageToolbarActions?.length
4192
+ ? messageSettings.messageToolbarActions
4193
+ : this.chatService.messageToolbarActions || [];
4194
+ }
4195
+ getFileActions() {
4196
+ const messageSettings = this.getMessageSettings();
4197
+ const actions = messageSettings?.fileActions?.length
4198
+ ? messageSettings.fileActions
4199
+ : this.chatService.fileActions || [];
4200
+ return transformActions(actions);
4201
+ }
4202
+ 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 });
4203
+ 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
4204
  {
3450
4205
  provide: ChatItem,
3451
4206
  useExisting: forwardRef(() => MessageComponent)
3452
4207
  }
3453
4208
  ], 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>
4209
+ <ng-container *ngIf="useCustomBubbleTemplate">
4210
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4211
+ </ng-container>
3480
4212
 
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
- }"
4213
+ <ng-container *ngIf="!useCustomBubbleTemplate">
4214
+ <time
4215
+ [attr.aria-hidden]="!selected"
4216
+ class="k-message-time"
4217
+ *ngIf="message.timestamp"
3492
4218
  >
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>
4219
+ {{ formatTimeStamp(message.timestamp) }}
4220
+ </time>
4221
+
4222
+ <ng-container *ngIf="message.typing">
4223
+ <div class="k-chat-bubble k-bubble">
4224
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
4225
+ <span></span>
4226
+ <span></span>
4227
+ <span></span>
3527
4228
  </div>
3528
4229
  </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)"
4230
+ </ng-container>
4231
+
4232
+ <ng-container *ngIf="!message.typing">
4233
+ <div
4234
+ class="k-chat-bubble k-bubble"
4235
+ *ngIf="useCustomContentTemplate"
4236
+ [attr.tabindex]="0"
4237
+ [ngClass]="{
4238
+ 'k-bubble-expandable': isMessageExpandable,
4239
+ 'k-expanded': isMessageExpanded,
4240
+ 'k-selected': selected,
4241
+ 'k-focus': selected,
4242
+ 'k-active': isActiveMessage
4243
+ }"
3536
4244
  >
3537
- <kendo-icon-wrapper
3538
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
3539
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4245
+ <div class="k-bubble-content">
4246
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4247
+ </div>
4248
+ <span
4249
+ class="k-bubble-expandable-indicator"
4250
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4251
+ [attr.tabindex]="'0'"
4252
+ [attr.role]="'button'"
4253
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4254
+ (mousedown)="chatService.toggleMessageState = true"
4255
+ (click)="toggleMessageState($event)"
3540
4256
  >
3541
- </kendo-icon-wrapper>
3542
- </span>
3543
- </div>
3544
- </ng-container>
4257
+ <kendo-icon-wrapper
4258
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4259
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4260
+ >
4261
+ </kendo-icon-wrapper>
4262
+ </span>
4263
+ </div>
3545
4264
 
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 }"
4265
+ <div
4266
+ class="k-chat-bubble k-bubble"
4267
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
4268
+ [attr.tabindex]="0"
4269
+ [ngClass]="{
4270
+ 'k-bubble-expandable': isMessageExpandable,
4271
+ 'k-expanded': isMessageExpanded,
4272
+ 'k-selected': selected,
4273
+ 'k-focus': selected,
4274
+ 'k-active': isActiveMessage
4275
+ }"
3554
4276
  >
3555
- </ng-template>
3556
- </ng-container>
3557
- <ng-container *ngIf="!statusTemplate?.templateRef">
3558
- {{ message.status }}
4277
+ <div class="k-bubble-content">
4278
+ <ng-container *ngIf="message.text || message.isDeleted">
4279
+ <div
4280
+ class="k-message-reference k-message-reference-receiver"
4281
+ *ngIf="message.replyToId && !message.isDeleted"
4282
+ (click)="onReplyReferenceClick($event, message.replyToId)"
4283
+ >
4284
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
4285
+ </div>
4286
+
4287
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
4288
+ {{ getDeletedMessageText() }}
4289
+ </span>
4290
+
4291
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
4292
+ <ng-container *ngFor="let part of parts">
4293
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
4294
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
4295
+ </ng-container>
4296
+ </span>
4297
+ </ng-container>
4298
+
4299
+ <ul
4300
+ class="k-chat-file-wrapper"
4301
+ *ngIf="hasFiles && !message.isDeleted"
4302
+ [ngClass]="{
4303
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
4304
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
4305
+ }"
4306
+ >
4307
+ <li
4308
+ *ngFor="let file of message.files"
4309
+ class="k-chat-file"
4310
+ [chatFile]="file"
4311
+ [fileActions]="fileActions"
4312
+ (actionClick)="onFileAction($event, file)"
4313
+ (actionsToggle)="onActionPopupChange($event)"
4314
+ (actionButtonClick)="onActionButtonClick($event)"
4315
+ ></li>
4316
+ </ul>
4317
+
4318
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
4319
+ <button
4320
+ kendoButton
4321
+ class="k-chat-download-button"
4322
+ fillMode="flat"
4323
+ icon="download"
4324
+ [svgIcon]="downloadIcon"
4325
+ [attr.title]="textFor('downloadAllFilesText')"
4326
+ (click)="onDownloadAll()"
4327
+ >{{ textFor('downloadAllFilesText') }}</button>
4328
+ </div>
4329
+ </div>
4330
+
4331
+ <span
4332
+ class="k-bubble-expandable-indicator"
4333
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4334
+ [attr.tabindex]="'0'"
4335
+ [attr.role]="'button'"
4336
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4337
+ (mousedown)="chatService.toggleMessageState = true"
4338
+ (click)="toggleMessageState($event)"
4339
+ >
4340
+ <kendo-icon-wrapper
4341
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4342
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4343
+ >
4344
+ </kendo-icon-wrapper>
4345
+ </span>
4346
+ </div>
3559
4347
  </ng-container>
3560
- </span>
3561
4348
 
3562
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
4349
+ <span class="k-message-status" *ngIf="message.status">
4350
+ <ng-container *ngIf="statusTemplate?.templateRef">
4351
+ <ng-template
4352
+ [ngTemplateOutlet]="statusTemplate.templateRef"
4353
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4354
+ >
4355
+ </ng-template>
4356
+ </ng-container>
4357
+ <ng-container *ngIf="!statusTemplate?.templateRef">
4358
+ {{ message.status }}
4359
+ </ng-container>
4360
+ </span>
4361
+ </ng-container>
4362
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
3563
4363
  <kendo-toolbar-button
3564
- *ngFor="let action of chatService.messageToolbarActions"
4364
+ *ngFor="let action of toolbarActions"
3565
4365
  fillMode="flat"
3566
4366
  [icon]="action.icon"
3567
4367
  [svgIcon]="action.svgIcon"
@@ -3571,28 +4371,7 @@ class MessageComponent extends ChatItem {
3571
4371
  >
3572
4372
  </kendo-toolbar-button>
3573
4373
  </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"] }] });
4374
+ `, 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
4375
  }
3597
4376
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, decorators: [{
3598
4377
  type: Component,
@@ -3605,117 +4384,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3605
4384
  }
3606
4385
  ],
3607
4386
  template: `
3608
- <time
3609
- [attr.aria-hidden]="!selected"
3610
- class="k-message-time"
3611
- *ngIf="message.timestamp"
3612
- >
3613
- {{ formatTimeStamp(message.timestamp) }}
3614
- </time>
4387
+ <ng-container *ngIf="useCustomBubbleTemplate">
4388
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4389
+ </ng-container>
3615
4390
 
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
- }"
4391
+ <ng-container *ngIf="!useCustomBubbleTemplate">
4392
+ <time
4393
+ [attr.aria-hidden]="!selected"
4394
+ class="k-message-time"
4395
+ *ngIf="message.timestamp"
3626
4396
  >
3627
- <div class="k-bubble-content">
3628
- <ng-container
3629
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
4397
+ {{ formatTimeStamp(message.timestamp) }}
4398
+ </time>
4399
+
4400
+ <ng-container *ngIf="message.typing">
4401
+ <div class="k-chat-bubble k-bubble">
4402
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
4403
+ <span></span>
4404
+ <span></span>
4405
+ <span></span>
4406
+ </div>
4407
+ </div>
4408
+ </ng-container>
4409
+
4410
+ <ng-container *ngIf="!message.typing">
4411
+ <div
4412
+ class="k-chat-bubble k-bubble"
4413
+ *ngIf="useCustomContentTemplate"
4414
+ [attr.tabindex]="0"
4415
+ [ngClass]="{
4416
+ 'k-bubble-expandable': isMessageExpandable,
4417
+ 'k-expanded': isMessageExpanded,
4418
+ 'k-selected': selected,
4419
+ 'k-focus': selected,
4420
+ 'k-active': isActiveMessage
4421
+ }"
4422
+ >
4423
+ <div class="k-bubble-content">
4424
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
4425
+ </div>
4426
+ <span
4427
+ class="k-bubble-expandable-indicator"
4428
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4429
+ [attr.tabindex]="'0'"
4430
+ [attr.role]="'button'"
4431
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4432
+ (mousedown)="chatService.toggleMessageState = true"
4433
+ (click)="toggleMessageState($event)"
3630
4434
  >
3631
- </ng-container>
4435
+ <kendo-icon-wrapper
4436
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4437
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4438
+ >
4439
+ </kendo-icon-wrapper>
4440
+ </span>
3632
4441
  </div>
3633
- </div>
3634
4442
 
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
- }"
3646
- >
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>
4443
+ <div
4444
+ class="k-chat-bubble k-bubble"
4445
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
4446
+ [attr.tabindex]="0"
4447
+ [ngClass]="{
4448
+ 'k-bubble-expandable': isMessageExpandable,
4449
+ 'k-expanded': isMessageExpanded,
4450
+ 'k-selected': selected,
4451
+ 'k-focus': selected,
4452
+ 'k-active': isActiveMessage
4453
+ }"
4454
+ >
4455
+ <div class="k-bubble-content">
4456
+ <ng-container *ngIf="message.text || message.isDeleted">
4457
+ <div
4458
+ class="k-message-reference k-message-reference-receiver"
4459
+ *ngIf="message.replyToId && !message.isDeleted"
4460
+ (click)="onReplyReferenceClick($event, message.replyToId)"
4461
+ >
4462
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
4463
+ </div>
4464
+
4465
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
4466
+ {{ getDeletedMessageText() }}
4467
+ </span>
4468
+
4469
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
4470
+ <ng-container *ngFor="let part of parts">
4471
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
4472
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
4473
+ </ng-container>
4474
+ </span>
4475
+ </ng-container>
4476
+
4477
+ <ul
4478
+ class="k-chat-file-wrapper"
4479
+ *ngIf="hasFiles && !message.isDeleted"
4480
+ [ngClass]="{
4481
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
4482
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
4483
+ }"
4484
+ >
4485
+ <li
4486
+ *ngFor="let file of message.files"
4487
+ class="k-chat-file"
4488
+ [chatFile]="file"
4489
+ [fileActions]="fileActions"
4490
+ (actionClick)="onFileAction($event, file)"
4491
+ (actionsToggle)="onActionPopupChange($event)"
4492
+ (actionButtonClick)="onActionButtonClick($event)"
4493
+ ></li>
4494
+ </ul>
4495
+
4496
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
4497
+ <button
4498
+ kendoButton
4499
+ class="k-chat-download-button"
4500
+ fillMode="flat"
4501
+ icon="download"
4502
+ [svgIcon]="downloadIcon"
4503
+ [attr.title]="textFor('downloadAllFilesText')"
4504
+ (click)="onDownloadAll()"
4505
+ >{{ textFor('downloadAllFilesText') }}</button>
3651
4506
  </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>
3681
4507
  </div>
3682
- </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)"
3690
- >
3691
- <kendo-icon-wrapper
3692
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
3693
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
3694
- >
3695
- </kendo-icon-wrapper>
3696
- </span>
3697
- </div>
3698
- </ng-container>
3699
4508
 
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 }"
3708
- >
3709
- </ng-template>
3710
- </ng-container>
3711
- <ng-container *ngIf="!statusTemplate?.templateRef">
3712
- {{ message.status }}
4509
+ <span
4510
+ class="k-bubble-expandable-indicator"
4511
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
4512
+ [attr.tabindex]="'0'"
4513
+ [attr.role]="'button'"
4514
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
4515
+ (mousedown)="chatService.toggleMessageState = true"
4516
+ (click)="toggleMessageState($event)"
4517
+ >
4518
+ <kendo-icon-wrapper
4519
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
4520
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
4521
+ >
4522
+ </kendo-icon-wrapper>
4523
+ </span>
4524
+ </div>
3713
4525
  </ng-container>
3714
- </span>
3715
4526
 
3716
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
4527
+ <span class="k-message-status" *ngIf="message.status">
4528
+ <ng-container *ngIf="statusTemplate?.templateRef">
4529
+ <ng-template
4530
+ [ngTemplateOutlet]="statusTemplate.templateRef"
4531
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
4532
+ >
4533
+ </ng-template>
4534
+ </ng-container>
4535
+ <ng-container *ngIf="!statusTemplate?.templateRef">
4536
+ {{ message.status }}
4537
+ </ng-container>
4538
+ </span>
4539
+ </ng-container>
4540
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
3717
4541
  <kendo-toolbar-button
3718
- *ngFor="let action of chatService.messageToolbarActions"
4542
+ *ngFor="let action of toolbarActions"
3719
4543
  fillMode="flat"
3720
4544
  [icon]="action.icon"
3721
4545
  [svgIcon]="action.svgIcon"
@@ -3725,36 +4549,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3725
4549
  >
3726
4550
  </kendo-toolbar-button>
3727
4551
  </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
4552
  `,
3750
4553
  standalone: true,
3751
- imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ContextMenuComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
4554
+ imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
3752
4555
  }]
3753
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }], propDecorators: { message: [{
4556
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { message: [{
3754
4557
  type: Input
3755
4558
  }], tabbable: [{
3756
4559
  type: Input
3757
- }], template: [{
4560
+ }], authorMessageContentTemplate: [{
4561
+ type: Input
4562
+ }], receiverMessageContentTemplate: [{
4563
+ type: Input
4564
+ }], messageContentTemplate: [{
4565
+ type: Input
4566
+ }], authorMessageTemplate: [{
4567
+ type: Input
4568
+ }], receiverMessageTemplate: [{
4569
+ type: Input
4570
+ }], messageTemplate: [{
3758
4571
  type: Input
3759
4572
  }], statusTemplate: [{
3760
4573
  type: Input
@@ -3762,8 +4575,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3762
4575
  type: Input
3763
4576
  }], authorId: [{
3764
4577
  type: Input
3765
- }], contextMenuVisibilityChange: [{
3766
- type: Output
3767
4578
  }], cssClass: [{
3768
4579
  type: HostBinding,
3769
4580
  args: ['class.k-message']
@@ -3776,9 +4587,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3776
4587
  }], tabIndex: [{
3777
4588
  type: HostBinding,
3778
4589
  args: ['attr.tabIndex']
3779
- }], onContextMenu: [{
3780
- type: HostListener,
3781
- args: ['contextmenu', ['$event']]
3782
4590
  }] } });
3783
4591
 
3784
4592
  /**
@@ -3894,10 +4702,10 @@ class MessageAttachmentsComponent extends ChatItem {
3894
4702
  scrollSubscription;
3895
4703
  direction;
3896
4704
  get showLeftArrow() {
3897
- return this.carousel && this.direction === 'rtl' ? this.scrollPosition > -1 : this.scrollPosition > 0;
4705
+ return this.carousel && (this.direction === 'rtl' ? this.scrollPosition > -1 : this.scrollPosition > 0);
3898
4706
  }
3899
4707
  get showRightArrow() {
3900
- return this.carousel && this.direction === 'rtl' ? this.scrollPosition < 0 : this.scrollPosition < 1;
4708
+ return this.carousel && (this.direction === 'rtl' ? this.scrollPosition < 0 : this.scrollPosition < 1);
3901
4709
  }
3902
4710
  carouselKeyHandlers = {
3903
4711
  [Keys.ArrowLeft]: (e) => this.navigateTo(e, this.direction === 'rtl' ? 1 : -1),
@@ -4100,6 +4908,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4100
4908
  args: ['item', { read: ElementRef }]
4101
4909
  }] } });
4102
4910
 
4911
+ /**
4912
+ * Defines a template for displaying user status in the Chat.
4913
+ *
4914
+ * To define a user status template, nest an `<ng-template>` tag with the `kendoChatUserStatusTemplate` directive inside the `<kendo-chat>` component.
4915
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
4916
+ *
4917
+ * @example
4918
+ * ```html
4919
+ * <kendo-chat>
4920
+ * <ng-template kendoChatUserStatusTemplate let-status>
4921
+ * <div>{{ status }}</div>
4922
+ * </ng-template>
4923
+ * </kendo-chat>
4924
+ * ```
4925
+ */
4926
+ class ChatUserStatusTemplateDirective {
4927
+ templateRef;
4928
+ constructor(templateRef) {
4929
+ this.templateRef = templateRef;
4930
+ }
4931
+ 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 });
4932
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ChatUserStatusTemplateDirective, isStandalone: true, selector: "[kendoChatUserStatusTemplate]", ngImport: i0 });
4933
+ }
4934
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatUserStatusTemplateDirective, decorators: [{
4935
+ type: Directive,
4936
+ args: [{
4937
+ selector: '[kendoChatUserStatusTemplate]',
4938
+ standalone: true
4939
+ }]
4940
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
4941
+ type: Optional
4942
+ }] }] });
4943
+
4103
4944
  /* eslint-disable @typescript-eslint/no-unused-vars */
4104
4945
  /* eslint-disable @typescript-eslint/no-explicit-any */
4105
4946
  /**
@@ -4120,9 +4961,15 @@ class MessageListComponent {
4120
4961
  return this._messages;
4121
4962
  }
4122
4963
  attachmentTemplate;
4964
+ authorMessageContentTemplate;
4965
+ receiverMessageContentTemplate;
4966
+ messageContentTemplate;
4967
+ authorMessageTemplate;
4968
+ receiverMessageTemplate;
4123
4969
  messageTemplate;
4124
4970
  timestampTemplate;
4125
4971
  statusTemplate;
4972
+ userStatusTemplate;
4126
4973
  localization;
4127
4974
  authorId;
4128
4975
  executeAction = new EventEmitter();
@@ -4140,6 +4987,7 @@ class MessageListComponent {
4140
4987
  [Keys.ArrowUp]: (e) => this.navigateTo(e, -1),
4141
4988
  [Keys.ArrowDown]: (e) => this.navigateTo(e, 1),
4142
4989
  [Keys.Tab]: (e) => this.onTabKeyDown(e),
4990
+ [Keys.F10]: (e) => e.shiftKey && this.openContextMenu(e),
4143
4991
  };
4144
4992
  constructor(element, intl, renderer, chatService, cdr) {
4145
4993
  this.element = element;
@@ -4152,9 +5000,25 @@ class MessageListComponent {
4152
5000
  const elRef = this.element.nativeElement;
4153
5001
  this.subs.add(this.renderer.listen(elRef, 'keydown', event => this.onKeydown(event)));
4154
5002
  this.subs.add(this.renderer.listen(elRef, 'focusout', event => this.onBlur(event)));
5003
+ this.subs.add(this.renderer.listen(elRef, 'click', event => {
5004
+ const messageComponent = this.findMessageComponentFromEvent(event);
5005
+ if (messageComponent) {
5006
+ this.select(messageComponent, event);
5007
+ }
5008
+ }));
5009
+ this.subs.add(this.renderer.listen(elRef, 'contextmenu', event => {
5010
+ event.preventDefault();
5011
+ const messageComponent = this.findMessageComponentFromEvent(event);
5012
+ if (messageComponent) {
5013
+ this.onContextMenuClick(messageComponent.message, event, messageComponent);
5014
+ }
5015
+ }));
4155
5016
  this.subs.add(this.chatService.replyReferenceClick$.subscribe((messageId) => {
4156
5017
  this.scrollToAndSelectMessage(messageId);
4157
5018
  }));
5019
+ this.subs.add(this.chatService.contextMenuVisibilityChange$.subscribe((isVisible) => {
5020
+ this.handleMenuClose(isVisible);
5021
+ }));
4158
5022
  }
4159
5023
  ngAfterViewInit() {
4160
5024
  this.selectedItem = this.items.last;
@@ -4168,9 +5032,31 @@ class MessageListComponent {
4168
5032
  onClick(message, event) {
4169
5033
  this.select(message, event);
4170
5034
  }
5035
+ onContextMenuClick(message, event, messageElement) {
5036
+ this.chatService.calculateContextMenuActions(this.isOwnMessage(message));
5037
+ if (this.chatService.calculatedContextMenuActions.length > 0) {
5038
+ this.chatService.messagesContextMenu.show({
5039
+ left: event.pageX,
5040
+ top: event.pageY,
5041
+ });
5042
+ this.chatService.activeMessageElement = messageElement;
5043
+ this.chatService.activeMessage = message;
5044
+ this.chatService.active = true;
5045
+ this.chatService.selectOnMenuClose = this.chatService.activeMessageElement?.selected;
5046
+ this.chatService.emit('contextMenuVisibilityChange', true);
5047
+ }
5048
+ }
4171
5049
  formatTimeStamp(date) {
4172
5050
  return this.intl.formatDate(date, { date: 'full' });
4173
5051
  }
5052
+ calculateMessageWidthMode(message) {
5053
+ const isOwn = this.isOwnMessage(message);
5054
+ const messageSettings = isOwn ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings;
5055
+ if (messageSettings?.messageWidthMode) {
5056
+ return messageSettings.messageWidthMode === 'full';
5057
+ }
5058
+ return this.chatService.messageWidthMode === 'full';
5059
+ }
4174
5060
  onKeydown(e) {
4175
5061
  // On some keyboards Numpad keys are used for Home/End/PageUp/PageDown.
4176
5062
  const code = normalizeNumpadKeys(e);
@@ -4230,6 +5116,9 @@ class MessageListComponent {
4230
5116
  this.select(null);
4231
5117
  }
4232
5118
  }
5119
+ textFor(key) {
5120
+ return this.localization.get(key);
5121
+ }
4233
5122
  onHomeOrEndKeyDown(key) {
4234
5123
  const items = this.items.toArray();
4235
5124
  if (key === 'home') {
@@ -4276,11 +5165,49 @@ class MessageListComponent {
4276
5165
  this.select(chatItem);
4277
5166
  }
4278
5167
  }
4279
- textFor(key) {
4280
- return this.localization.get(key);
5168
+ openContextMenu(event) {
5169
+ event.preventDefault();
5170
+ const messageComponent = this.findMessageComponentFromActiveElement();
5171
+ if (messageComponent) {
5172
+ const messageContentElement = messageComponent.element.nativeElement.querySelector('.k-chat-bubble');
5173
+ if (messageContentElement) {
5174
+ const rect = messageContentElement?.getBoundingClientRect();
5175
+ this.onContextMenuClick(messageComponent.message, {
5176
+ pageX: rect.left + (rect.width / 2) + window.scrollX,
5177
+ pageY: rect.top + (rect.height / 2) + window.scrollY
5178
+ }, messageComponent);
5179
+ }
5180
+ else {
5181
+ const messageElement = messageComponent.element.nativeElement;
5182
+ const rect = messageElement?.getBoundingClientRect();
5183
+ this.onContextMenuClick(messageComponent.message, {
5184
+ pageX: rect.left + (rect.width / 2) + window.scrollX,
5185
+ pageY: rect.top + (rect.height / 2) + window.scrollY
5186
+ }, messageComponent);
5187
+ }
5188
+ }
5189
+ }
5190
+ findMessageComponentFromActiveElement() {
5191
+ const activeElement = document.activeElement;
5192
+ const messageComponents = this.items.filter(item => item instanceof MessageComponent);
5193
+ return messageComponents.find(component => {
5194
+ const componentElement = component.element?.nativeElement;
5195
+ return componentElement && (componentElement === activeElement || componentElement.contains(activeElement));
5196
+ });
5197
+ }
5198
+ findMessageComponentFromEvent(event) {
5199
+ const target = event.target;
5200
+ const clickedElement = target?.closest('.k-message');
5201
+ if (!clickedElement)
5202
+ return undefined;
5203
+ const messageComponents = this.items.filter(item => item instanceof MessageComponent);
5204
+ return messageComponents.find(component => {
5205
+ const componentElement = component.element?.nativeElement;
5206
+ return componentElement && (componentElement === clickedElement || componentElement.contains(clickedElement));
5207
+ });
4281
5208
  }
4282
5209
  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: `
5210
+ 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
5211
  <ng-container *ngFor="let group of view; last as lastGroup; trackBy: trackGroup">
4285
5212
  <ng-container [ngSwitch]="group.type">
4286
5213
  <div
@@ -4301,10 +5228,10 @@ class MessageListComponent {
4301
5228
  [class.k-message-group-sender]="isOwnMessage(group.messages[0])"
4302
5229
  [class.k-message-group-receiver]="!isOwnMessage(group.messages[0])"
4303
5230
  [class.k-no-avatar]="!group.author.avatarUrl"
4304
- [class.k-message-group-full-width]="chatService.messageWidthMode === 'full'"
5231
+ [class.k-message-group-full-width]="calculateMessageWidthMode(group.messages[0])"
4305
5232
  >
4306
5233
  <div
4307
- *ngIf="group.author.avatarUrl"
5234
+ *ngIf="!userStatusTemplate?.templateRef && group.author.avatarUrl"
4308
5235
  class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4309
5236
  >
4310
5237
  <span class="k-avatar-image">
@@ -4314,6 +5241,25 @@ class MessageListComponent {
4314
5241
  />
4315
5242
  </span>
4316
5243
  </div>
5244
+ <div class="k-chat-user-status-wrapper" *ngIf="group.author.avatarUrl && userStatusTemplate?.templateRef">
5245
+ <div
5246
+ class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
5247
+ >
5248
+ <span class="k-avatar-image">
5249
+ <img
5250
+ [attr.src]="group.author.avatarUrl"
5251
+ [alt]="group.author.avatarAltText"
5252
+ />
5253
+ </span>
5254
+ </div>
5255
+ <div class="k-chat-user-status" *ngIf="userStatusTemplate?.templateRef">
5256
+ <ng-template
5257
+ [ngTemplateOutlet]="userStatusTemplate.templateRef"
5258
+ [ngTemplateOutletContext]="{ $implicit: group.messages.at(-1) }"
5259
+ >
5260
+ </ng-template>
5261
+ </div>
5262
+ </div>
4317
5263
  <div class="k-message-group-content">
4318
5264
  <p *ngIf="group.author.name" class="k-message-author">{{ group.author.name }}</p>
4319
5265
  <ng-container
@@ -4330,13 +5276,14 @@ class MessageListComponent {
4330
5276
  <kendo-chat-message #message
4331
5277
  [message]="msg"
4332
5278
  [tabbable]="lastGroup && lastMessage"
4333
- [template]="messageTemplate"
5279
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
5280
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
5281
+ [messageContentTemplate]="messageContentTemplate"
5282
+ [authorMessageTemplate]="authorMessageTemplate"
5283
+ [receiverMessageTemplate]="receiverMessageTemplate"
5284
+ [messageTemplate]="messageTemplate"
4334
5285
  [statusTemplate]="statusTemplate"
4335
5286
  [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
5287
  >
4341
5288
  </kendo-chat-message>
4342
5289
 
@@ -4365,6 +5312,7 @@ class MessageListComponent {
4365
5312
  <kendo-chat-suggested-actions #actions
4366
5313
  *ngSwitchCase="'action-group'"
4367
5314
  [actions]="group.actions"
5315
+ type="action"
4368
5316
  [tabbable]="lastGroup"
4369
5317
  (dispatchAction)="dispatchAction($event, last(view))"
4370
5318
  (click)="select(actions, $event)"
@@ -4375,7 +5323,7 @@ class MessageListComponent {
4375
5323
  </ng-container>
4376
5324
  <kendo-resize-sensor (resize)="onResize()">
4377
5325
  </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"] }] });
5326
+ `, 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
5327
  }
4380
5328
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageListComponent, decorators: [{
4381
5329
  type: Component,
@@ -4402,10 +5350,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4402
5350
  [class.k-message-group-sender]="isOwnMessage(group.messages[0])"
4403
5351
  [class.k-message-group-receiver]="!isOwnMessage(group.messages[0])"
4404
5352
  [class.k-no-avatar]="!group.author.avatarUrl"
4405
- [class.k-message-group-full-width]="chatService.messageWidthMode === 'full'"
5353
+ [class.k-message-group-full-width]="calculateMessageWidthMode(group.messages[0])"
4406
5354
  >
4407
5355
  <div
4408
- *ngIf="group.author.avatarUrl"
5356
+ *ngIf="!userStatusTemplate?.templateRef && group.author.avatarUrl"
4409
5357
  class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
4410
5358
  >
4411
5359
  <span class="k-avatar-image">
@@ -4415,6 +5363,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4415
5363
  />
4416
5364
  </span>
4417
5365
  </div>
5366
+ <div class="k-chat-user-status-wrapper" *ngIf="group.author.avatarUrl && userStatusTemplate?.templateRef">
5367
+ <div
5368
+ class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
5369
+ >
5370
+ <span class="k-avatar-image">
5371
+ <img
5372
+ [attr.src]="group.author.avatarUrl"
5373
+ [alt]="group.author.avatarAltText"
5374
+ />
5375
+ </span>
5376
+ </div>
5377
+ <div class="k-chat-user-status" *ngIf="userStatusTemplate?.templateRef">
5378
+ <ng-template
5379
+ [ngTemplateOutlet]="userStatusTemplate.templateRef"
5380
+ [ngTemplateOutletContext]="{ $implicit: group.messages.at(-1) }"
5381
+ >
5382
+ </ng-template>
5383
+ </div>
5384
+ </div>
4418
5385
  <div class="k-message-group-content">
4419
5386
  <p *ngIf="group.author.name" class="k-message-author">{{ group.author.name }}</p>
4420
5387
  <ng-container
@@ -4431,13 +5398,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4431
5398
  <kendo-chat-message #message
4432
5399
  [message]="msg"
4433
5400
  [tabbable]="lastGroup && lastMessage"
4434
- [template]="messageTemplate"
5401
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
5402
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
5403
+ [messageContentTemplate]="messageContentTemplate"
5404
+ [authorMessageTemplate]="authorMessageTemplate"
5405
+ [receiverMessageTemplate]="receiverMessageTemplate"
5406
+ [messageTemplate]="messageTemplate"
4435
5407
  [statusTemplate]="statusTemplate"
4436
5408
  [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
5409
  >
4442
5410
  </kendo-chat-message>
4443
5411
 
@@ -4466,6 +5434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4466
5434
  <kendo-chat-suggested-actions #actions
4467
5435
  *ngSwitchCase="'action-group'"
4468
5436
  [actions]="group.actions"
5437
+ type="action"
4469
5438
  [tabbable]="lastGroup"
4470
5439
  (dispatchAction)="dispatchAction($event, last(view))"
4471
5440
  (click)="select(actions, $event)"
@@ -4484,12 +5453,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4484
5453
  type: Input
4485
5454
  }], attachmentTemplate: [{
4486
5455
  type: Input
5456
+ }], authorMessageContentTemplate: [{
5457
+ type: Input
5458
+ }], receiverMessageContentTemplate: [{
5459
+ type: Input
5460
+ }], messageContentTemplate: [{
5461
+ type: Input
5462
+ }], authorMessageTemplate: [{
5463
+ type: Input
5464
+ }], receiverMessageTemplate: [{
5465
+ type: Input
4487
5466
  }], messageTemplate: [{
4488
5467
  type: Input
4489
5468
  }], timestampTemplate: [{
4490
5469
  type: Input
4491
5470
  }], statusTemplate: [{
4492
5471
  type: Input
5472
+ }], userStatusTemplate: [{
5473
+ type: Input
4493
5474
  }], localization: [{
4494
5475
  type: Input
4495
5476
  }], authorId: [{
@@ -4652,8 +5633,16 @@ let Messages$1 = class Messages extends ComponentMessages {
4652
5633
  * Sets the title of the DropDownButton that opens the File actions.
4653
5634
  */
4654
5635
  fileActionsTitle;
5636
+ /**
5637
+ * Sets the title of the button that shows the **Scroll right** when the suggestions list is scrollable with buttons.
5638
+ */
5639
+ nextSuggestionsButtonTitle;
5640
+ /**
5641
+ * Sets the title of the button that shows the **Scroll left** when the suggestions list is scrollable with buttons.
5642
+ */
5643
+ previousSuggestionsButtonTitle;
4655
5644
  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 });
5645
+ 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
5646
  };
4658
5647
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: Messages$1, decorators: [{
4659
5648
  type: Directive,
@@ -4693,6 +5682,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4693
5682
  type: Input
4694
5683
  }], fileActionsTitle: [{
4695
5684
  type: Input
5685
+ }], nextSuggestionsButtonTitle: [{
5686
+ type: Input
5687
+ }], previousSuggestionsButtonTitle: [{
5688
+ type: Input
4696
5689
  }] } });
4697
5690
 
4698
5691
  // eslint-disable no-forward-ref
@@ -4755,6 +5748,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4755
5748
  }]
4756
5749
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
4757
5750
 
5751
+ /**
5752
+ * Defines a template for displaying custom content when there are no messages in the Chat.
5753
+ *
5754
+ * To define an empty Chat template, nest an `<ng-template>` tag with the `kendoChatNoDataTemplate` directive inside the `<kendo-chat>` component.
5755
+ * For more information, refer to the article on [message templates](slug:message_templates_chat).
5756
+ *
5757
+ * @example
5758
+ * ```html
5759
+ * <kendo-chat>
5760
+ * <ng-template kendoChatNoDataTemplate>
5761
+ * <div>No messages.</div>
5762
+ * </ng-template>
5763
+ * </kendo-chat>
5764
+ * ```
5765
+ */
5766
+ class NoDataTemplateDirective {
5767
+ templateRef;
5768
+ constructor(templateRef) {
5769
+ this.templateRef = templateRef;
5770
+ }
5771
+ 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 });
5772
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: NoDataTemplateDirective, isStandalone: true, selector: "[kendoChatNoDataTemplate]", ngImport: i0 });
5773
+ }
5774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NoDataTemplateDirective, decorators: [{
5775
+ type: Directive,
5776
+ args: [{
5777
+ selector: '[kendoChatNoDataTemplate]',
5778
+ standalone: true
5779
+ }]
5780
+ }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
5781
+ type: Optional
5782
+ }] }] });
5783
+
4758
5784
  /**
4759
5785
  * Represents the [Kendo UI Chat component for Angular](slug:overview_convui).
4760
5786
  *
@@ -4772,7 +5798,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4772
5798
  * ```
4773
5799
  *
4774
5800
  * @remarks
4775
- * Supported children components are: {@link CustomMessagesComponent}, {@link HeroCardComponent}, {@link AttachmentTemplateDirective}, {@link ChatHeaderTemplateDirective}, {@link ChatMessageBoxTemplateDirective}, {@link MessageTemplateDirective}, {@link ChatStatusTemplateDirective}, {@link ChatSuggestionTemplateDirective}, {@link ChatTimestampTemplateDirective}.
5801
+ * 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
5802
  */
4777
5803
  class ChatComponent {
4778
5804
  localization;
@@ -4781,13 +5807,13 @@ class ChatComponent {
4781
5807
  element;
4782
5808
  chatService;
4783
5809
  /**
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).
5810
+ * Sets the Chat messages.
5811
+ * Accepts an array of `Message` objects, but can also accept custom data structures.
5812
+ * For more information, check [Data Binding](slug:databinding_chat) section in the documentation.
4787
5813
  */
4788
5814
  messages;
4789
5815
  /**
4790
- * Specifies the id representing the local user.
5816
+ * Sets the ID that represents the local user.
4791
5817
  */
4792
5818
  authorId;
4793
5819
  /**
@@ -4815,13 +5841,14 @@ class ChatComponent {
4815
5841
  */
4816
5842
  placeholder;
4817
5843
  /**
4818
- * Switches the width of the message between the predefined options.
5844
+ * Controls the width of the message between the predefined options.
4819
5845
  *
4820
5846
  * @default 'standard'
4821
5847
  */
4822
5848
  messageWidthMode = 'standard';
4823
5849
  /**
4824
5850
  * Enables the expand or collapse functionality for messages.
5851
+ *
4825
5852
  * @default false
4826
5853
  */
4827
5854
  allowMessageCollapse = false;
@@ -4838,24 +5865,34 @@ class ChatComponent {
4838
5865
  */
4839
5866
  enableFileSelect = true;
4840
5867
  /**
4841
- * Specifies the actions available in the message toolbar.
5868
+ * Sets the actions of the message toolbar.
4842
5869
  * These actions display in the message toolbar and let you perform specific operations on the message.
5870
+ *
4843
5871
  * @default []
4844
5872
  */
4845
5873
  messageToolbarActions = [];
4846
5874
  /**
4847
5875
  * Sets the value of the Message Box.
5876
+ *
4848
5877
  * @default ''
4849
5878
  */
4850
5879
  inputValue = '';
4851
5880
  /**
4852
- * Specifies the default actions available in the message context menu.
5881
+ * Sets the settings for the author's messages.
5882
+ */
5883
+ authorMessageSettings;
5884
+ /**
5885
+ * Sets the settings for the receivers' messages.
5886
+ */
5887
+ receiverMessageSettings;
5888
+ /**
5889
+ * Sets the default actions that display in the message context menu.
4853
5890
  *
4854
5891
  * @default [{ id: 'copy', label: 'Copy', icon: 'copy', svgIcon: copyIcon, disabled: false }, { id: 'reply', label: 'Reply', icon: 'undo', svgIcon: undoIcon, disabled: false }]
4855
5892
  */
4856
5893
  defaultContextMenuActions = CONTEXT_MENU_ACTIONS;
4857
5894
  /**
4858
- * Specifies the actions available in the message as a context menu.
5895
+ * Sets the actions that display in the message as a context menu.
4859
5896
  * These actions display as menu items and let you perform specific operations on the message.
4860
5897
  * The default actions are `copy` and `reply` and are defined by their `id`.
4861
5898
  */
@@ -4866,11 +5903,13 @@ class ChatComponent {
4866
5903
  return this._messageContextMenuActions;
4867
5904
  }
4868
5905
  /**
4869
- * Specifies the default actions available in the file actions DropDownButton.
5906
+ * Sets the default actions that display in the file actions DropDownButton.
5907
+ *
5908
+ * @default [{ id: 'download', label: 'Download', icon: 'download', svgIcon: downloadIcon, disabled: false }]
4870
5909
  */
4871
5910
  defaultFileActions = FILE_ACTIONS;
4872
5911
  /**
4873
- * Specifies the actions available in the file as items of a DropDownButton.
5912
+ * Sets the actions that display in the file as items of a DropDownButton.
4874
5913
  * These actions display when you click the file DropDownButton and let you perform specific operations on the file.
4875
5914
  * The default action is `download` and is defined by its `id`.
4876
5915
  *
@@ -4883,17 +5922,40 @@ class ChatComponent {
4883
5922
  return this._fileActions;
4884
5923
  }
4885
5924
  /**
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 []
5925
+ * Sets the layout of the files in the message bubble.
5926
+ *
5927
+ * @default 'vertical'
4889
5928
  */
4890
- suggestions = [];
5929
+ set messageFilesLayout(layout) {
5930
+ this.chatService.messageFilesLayout = layout;
5931
+ }
5932
+ /**
5933
+ * Sets the layout of the suggestions above the message input box.
5934
+ *
5935
+ * @default 'scroll'
5936
+ */
5937
+ set suggestionsLayout(layoutMode) {
5938
+ if (layoutMode) {
5939
+ this.chatService.suggestionsLayout = layoutMode;
5940
+ }
5941
+ }
4891
5942
  /**
4892
- * Sets the visibility of the message toolbar.
5943
+ * Sets the layout of the quick actions suggested below the messages.
4893
5944
  *
4894
- * @default 'hidden'
5945
+ * @default 'scroll'
4895
5946
  */
4896
- messageToolbarVisibility = 'hidden';
5947
+ set quickActionsLayout(layoutMode) {
5948
+ if (layoutMode) {
5949
+ this.chatService.quickActionsLayout = layoutMode;
5950
+ }
5951
+ }
5952
+ /**
5953
+ * Sets the suggestions that display in the message input box.
5954
+ * Suggestions display as a list of clickable items that let you quickly insert predefined text into the message input.
5955
+ *
5956
+ * @default []
5957
+ */
5958
+ suggestions = [];
4897
5959
  /**
4898
5960
  * Sets the send button settings for the Chat component.
4899
5961
  * Allows customization of the send button appearance, icons and disabled state.
@@ -4912,53 +5974,53 @@ class ChatComponent {
4912
5974
  return this._modelFields;
4913
5975
  }
4914
5976
  /**
4915
- * Emits when the user sends a message by clicking the **Send** button or pressing **Enter**.
5977
+ * Fires when the user sends a message by clicking the **Send** button or pressing **Enter**.
4916
5978
  *
4917
5979
  * The message is not automatically added to the `messages` array.
4918
5980
  */
4919
5981
  sendMessage = new EventEmitter();
4920
5982
  /**
4921
- * Emits when the user clicks a quick action button in the message toolbar.
5983
+ * Fires when the user clicks a quick action button in the message toolbar.
4922
5984
  */
4923
5985
  toolbarActionClick = new EventEmitter();
4924
5986
  /**
4925
- * Emits when the user clicks an action in the message context menu.
5987
+ * Fires when the user clicks an action in the message context menu.
4926
5988
  */
4927
5989
  contextMenuActionClick = new EventEmitter();
4928
5990
  /**
4929
- * Emits when the user clicks an action in the file context menu.
5991
+ * Fires when the user clicks an action in the file context menu.
4930
5992
  */
4931
5993
  fileActionClick = new EventEmitter();
4932
5994
  /**
4933
- * Emits when the user clicks an action in the file context menu.
5995
+ * Fires when the user clicks an action in the file context menu.
4934
5996
  */
4935
5997
  download = new EventEmitter();
4936
5998
  /**
4937
- * Emits when the user clicks a quick action button.
5999
+ * Fires when the user clicks a quick action button.
4938
6000
  * The Chat internally handles [known actions](slug:api_conversational-ui_actiontype) such as `reply`, `openUrl`, and `call`.
4939
6001
  *
4940
6002
  * The event is preventable. Calling [`preventDefault`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) suppresses the built-in action.
4941
6003
  */
4942
6004
  executeAction = new EventEmitter();
4943
6005
  /**
4944
- * Emits when the user clicks a suggestion in the message input box.
6006
+ * Fires when the user clicks a suggestion in the message input box.
4945
6007
  */
4946
6008
  suggestionExecute = new EventEmitter();
4947
6009
  /**
4948
- * Emits when the user selects a file in the message input box.
6010
+ * Fires when the user selects a file in the message input box.
4949
6011
  */
4950
6012
  fileSelect = new EventEmitter();
4951
6013
  /**
4952
- * Emits when the user removes a file from the message input box.
6014
+ * Fires when the user removes a file from the message input box.
4953
6015
  */
4954
6016
  fileRemove = new EventEmitter();
4955
6017
  /**
4956
- * Emits when the user unpins the pinned message.
6018
+ * Fires when the user unpins the pinned message.
4957
6019
  * This event triggers when the user clicks the delete button on the pinned message.
4958
6020
  */
4959
6021
  unpin = new EventEmitter();
4960
6022
  /**
4961
- * Emits when the user types in the message input box.
6023
+ * Fires when the user types in the message input box.
4962
6024
  */
4963
6025
  inputValueChange = new EventEmitter();
4964
6026
  get className() {
@@ -4967,13 +6029,23 @@ class ChatComponent {
4967
6029
  get dirAttr() {
4968
6030
  return this.direction;
4969
6031
  }
6032
+ set messagesContextMenu(contextMenu) {
6033
+ this.chatService.messagesContextMenu = contextMenu;
6034
+ }
4970
6035
  attachmentTemplate;
4971
6036
  chatHeaderTemplate;
6037
+ chatNoDataTemplate;
6038
+ authorMessageContentTemplate;
6039
+ receiverMessageContentTemplate;
6040
+ messageContentTemplate;
6041
+ authorMessageTemplate;
6042
+ receiverMessageTemplate;
4972
6043
  messageTemplate;
4973
6044
  timestampTemplate;
4974
6045
  suggestionTemplate;
4975
6046
  statusTemplate;
4976
6047
  messageBoxTemplate;
6048
+ userStatusTemplate;
4977
6049
  messageBox;
4978
6050
  /**
4979
6051
  * @hidden
@@ -4981,17 +6053,30 @@ class ChatComponent {
4981
6053
  messageList;
4982
6054
  /**
4983
6055
  * @hidden
4984
- * Returns processed messages when model fields are used, otherwise returns original messages
6056
+ * Returns processed messages when model fields are used, otherwise returns original messages.
4985
6057
  */
4986
6058
  get processedMessages() {
4987
6059
  if (!this.messages || this.messages.length === 0) {
4988
6060
  return [];
4989
6061
  }
4990
6062
  if (this._modelFields && Object.keys(this._modelFields).some(key => this._modelFields[key] !== defaultModelFields[key])) {
4991
- return processMessages(this.messages, this._modelFields);
6063
+ if (this.messages !== this._lastMessagesReference || this._modelFields !== this._lastModelFields) {
6064
+ this._cachedProcessedMessages = processMessages(this.messages, this._modelFields);
6065
+ this._lastMessagesReference = this.messages;
6066
+ this._lastModelFields = this._modelFields;
6067
+ }
6068
+ return this._cachedProcessedMessages;
4992
6069
  }
4993
6070
  return this.messages;
4994
6071
  }
6072
+ /**
6073
+ * Gets the actions available in the message context menu.
6074
+ *
6075
+ * @hidden
6076
+ */
6077
+ get contextMenuActions() {
6078
+ return transformActions(this.chatService.calculatedContextMenuActions);
6079
+ }
4995
6080
  /**
4996
6081
  * @hidden
4997
6082
  */
@@ -5019,6 +6104,9 @@ class ChatComponent {
5019
6104
  _modelFields = defaultModelFields;
5020
6105
  _messageContextMenuActions = CONTEXT_MENU_ACTIONS;
5021
6106
  _fileActions = FILE_ACTIONS;
6107
+ _cachedProcessedMessages = [];
6108
+ _lastMessagesReference = null;
6109
+ _lastModelFields = null;
5022
6110
  constructor(localization, zone, renderer, element, chatService) {
5023
6111
  this.localization = localization;
5024
6112
  this.zone = zone;
@@ -5043,7 +6131,8 @@ class ChatComponent {
5043
6131
  this.chatService.messageToolbarActions = this.messageToolbarActions;
5044
6132
  this.chatService.messageContextMenuActions = this.messageContextMenuActions;
5045
6133
  this.chatService.fileActions = this.fileActions;
5046
- this.chatService.messageToolbarVisibility = this.messageToolbarVisibility;
6134
+ this.chatService.authorMessageSettings = this.authorMessageSettings;
6135
+ this.chatService.receiverMessageSettings = this.receiverMessageSettings;
5047
6136
  this.chatService.messages = this.processedMessages || [];
5048
6137
  this.chatService.chatElement = this.messageList;
5049
6138
  this.subs.add(this.chatService.toolbarAction$.subscribe((actionEvent) => {
@@ -5091,7 +6180,8 @@ class ChatComponent {
5091
6180
  'messageToolbarActions',
5092
6181
  'messageContextMenuActions',
5093
6182
  'fileActions',
5094
- 'messageToolbarVisibility'
6183
+ 'authorMessageSettings',
6184
+ 'receiverMessageSettings'
5095
6185
  ], changes);
5096
6186
  }
5097
6187
  /**
@@ -5140,6 +6230,43 @@ class ChatComponent {
5140
6230
  this.chatService.scrollToMessage(this.pinnedMessage?.id);
5141
6231
  }
5142
6232
  }
6233
+ /**
6234
+ * @hidden
6235
+ */
6236
+ onContextMenuAction(action) {
6237
+ if (action.id === 'reply') {
6238
+ this.chatService.reply = this.chatService.activeMessage;
6239
+ }
6240
+ if (action.id === 'copy') {
6241
+ navigator.clipboard.writeText(this.chatService.activeMessage.text);
6242
+ }
6243
+ this.chatService.emit('contextMenuAction', { action, message: this.chatService.activeMessage });
6244
+ }
6245
+ /**
6246
+ * @hidden
6247
+ */
6248
+ handleMenuClose(event) {
6249
+ if (event) {
6250
+ const originalEvent = event.originalEvent;
6251
+ originalEvent && this.onActionButtonClick(originalEvent);
6252
+ }
6253
+ this.chatService.activeMessage = null;
6254
+ this.chatService.emit('contextMenuVisibilityChange', false);
6255
+ if (this.chatService.selectOnMenuClose) {
6256
+ this.chatService.activeMessageElement.selected = true;
6257
+ this.chatService.focusActiveMessageElement();
6258
+ }
6259
+ }
6260
+ /**
6261
+ * @hidden
6262
+ */
6263
+ onActionButtonClick(event) {
6264
+ const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
6265
+ const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
6266
+ if (clickOutsideMessage && !menuItemClick) {
6267
+ this.chatService.selectOnMenuClose = false;
6268
+ }
6269
+ }
5143
6270
  findLastPinnedMessage() {
5144
6271
  return [...this.processedMessages].reverse().find((message) => message.isPinned);
5145
6272
  }
@@ -5163,14 +6290,15 @@ class ChatComponent {
5163
6290
  });
5164
6291
  }
5165
6292
  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: [
6293
+ 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", 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
6294
  LocalizationService,
5168
6295
  ChatService,
6296
+ SuggestionsScrollService,
5169
6297
  {
5170
6298
  provide: L10N_PREFIX,
5171
6299
  useValue: 'kendo.chat'
5172
6300
  }
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: `
6301
+ ], 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
6302
  <ng-container kendoChatLocalizedMessages
5175
6303
  i18n-deletedMessageSenderText="kendo.chat.deletedMessageSenderText|The text that is displayed when the sender deletes a message"
5176
6304
  deletedMessageSenderText="You removed this message."
@@ -5219,10 +6347,16 @@ class ChatComponent {
5219
6347
 
5220
6348
  i18n-downloadAllFilesText="kendo.chat.downloadAllFilesText|Sets the text that is displayed in the download section of the message."
5221
6349
  downloadAllFilesText="Download all"
6350
+
6351
+ i18n-previousSuggestionsButtonTitle="kendo.chat.previousSuggestionsButtonTitle|The title of the button that scrolls to the previous suggestions"
6352
+ previousSuggestionsButtonTitle="Scroll left"
6353
+
6354
+ i18n-nextSuggestionsButtonTitle="kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions"
6355
+ nextSuggestionsButtonTitle="Scroll right"
5222
6356
  >
5223
6357
  </ng-container>
5224
6358
 
5225
- <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky">
6359
+ <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky" themeColor="inherit">
5226
6360
  <ng-container *ngTemplateOutlet="chatHeaderTemplate.templateRef"></ng-container>
5227
6361
  </kendo-appbar>
5228
6362
  <div class="k-message-reference k-message-reference-receiver k-message-pinned" *ngIf="pinnedMessage" (click)="scrollToPinnedMessage()">
@@ -5238,7 +6372,7 @@ class ChatComponent {
5238
6372
  </div>
5239
6373
  <div
5240
6374
  #messageList
5241
- class="k-message-list k-avatars"
6375
+ class="k-message-list"
5242
6376
  aria-live="polite"
5243
6377
  role="log"
5244
6378
  kendoChatScrollAnchor
@@ -5246,19 +6380,32 @@ class ChatComponent {
5246
6380
  #anchor="scrollAnchor"
5247
6381
  [(autoScroll)]="autoScroll"
5248
6382
  >
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>
6383
+ <div *ngIf="processedMessages && processedMessages.length === 0; else chatMessageList" class="k-message-list-content k-message-list-content-empty">
6384
+ <ng-template
6385
+ [ngTemplateOutlet]="chatNoDataTemplate?.templateRef">
6386
+ </ng-template>
6387
+ </div>
6388
+ <ng-template #chatMessageList>
6389
+ <kendo-chat-message-list
6390
+ [messages]="processedMessages"
6391
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
6392
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
6393
+ [messageContentTemplate]="messageContentTemplate"
6394
+ [authorMessageTemplate]="authorMessageTemplate"
6395
+ [receiverMessageTemplate]="receiverMessageTemplate"
6396
+ [messageTemplate]="messageTemplate"
6397
+ [timestampTemplate]="timestampTemplate"
6398
+ [statusTemplate]="statusTemplate"
6399
+ [userStatusTemplate]="userStatusTemplate"
6400
+ [localization]="localizationText"
6401
+ [attachmentTemplate]="attachmentTemplate"
6402
+ [authorId]="authorId"
6403
+ (executeAction)="dispatchAction($event)"
6404
+ (resize)="anchor.scrollToBottom()"
6405
+ (navigate)="this.autoScroll = false"
6406
+ >
6407
+ </kendo-chat-message-list>
6408
+ </ng-template>
5262
6409
  </div>
5263
6410
  <kendo-message-box
5264
6411
  #messageBox
@@ -5276,7 +6423,16 @@ class ChatComponent {
5276
6423
  (fileRemove)="fileRemove.emit($event)"
5277
6424
  >
5278
6425
  </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"] }] });
6426
+
6427
+ <kendo-contextmenu
6428
+ #messagesContextMenu
6429
+ [items]="contextMenuActions"
6430
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
6431
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
6432
+ (popupClose)="handleMenuClose($event)"
6433
+ (select)="onContextMenuAction($event.item.originalAction)"
6434
+ ></kendo-contextmenu>
6435
+ `, 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
6436
  }
5281
6437
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComponent, decorators: [{
5282
6438
  type: Component,
@@ -5284,6 +6440,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5284
6440
  providers: [
5285
6441
  LocalizationService,
5286
6442
  ChatService,
6443
+ SuggestionsScrollService,
5287
6444
  {
5288
6445
  provide: L10N_PREFIX,
5289
6446
  useValue: 'kendo.chat'
@@ -5339,10 +6496,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5339
6496
 
5340
6497
  i18n-downloadAllFilesText="kendo.chat.downloadAllFilesText|Sets the text that is displayed in the download section of the message."
5341
6498
  downloadAllFilesText="Download all"
6499
+
6500
+ i18n-previousSuggestionsButtonTitle="kendo.chat.previousSuggestionsButtonTitle|The title of the button that scrolls to the previous suggestions"
6501
+ previousSuggestionsButtonTitle="Scroll left"
6502
+
6503
+ i18n-nextSuggestionsButtonTitle="kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions"
6504
+ nextSuggestionsButtonTitle="Scroll right"
5342
6505
  >
5343
6506
  </ng-container>
5344
6507
 
5345
- <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky">
6508
+ <kendo-appbar *ngIf="chatHeaderTemplate" class="k-chat-header" positionMode="sticky" themeColor="inherit">
5346
6509
  <ng-container *ngTemplateOutlet="chatHeaderTemplate.templateRef"></ng-container>
5347
6510
  </kendo-appbar>
5348
6511
  <div class="k-message-reference k-message-reference-receiver k-message-pinned" *ngIf="pinnedMessage" (click)="scrollToPinnedMessage()">
@@ -5358,7 +6521,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5358
6521
  </div>
5359
6522
  <div
5360
6523
  #messageList
5361
- class="k-message-list k-avatars"
6524
+ class="k-message-list"
5362
6525
  aria-live="polite"
5363
6526
  role="log"
5364
6527
  kendoChatScrollAnchor
@@ -5366,19 +6529,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5366
6529
  #anchor="scrollAnchor"
5367
6530
  [(autoScroll)]="autoScroll"
5368
6531
  >
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>
6532
+ <div *ngIf="processedMessages && processedMessages.length === 0; else chatMessageList" class="k-message-list-content k-message-list-content-empty">
6533
+ <ng-template
6534
+ [ngTemplateOutlet]="chatNoDataTemplate?.templateRef">
6535
+ </ng-template>
6536
+ </div>
6537
+ <ng-template #chatMessageList>
6538
+ <kendo-chat-message-list
6539
+ [messages]="processedMessages"
6540
+ [authorMessageContentTemplate]="authorMessageContentTemplate"
6541
+ [receiverMessageContentTemplate]="receiverMessageContentTemplate"
6542
+ [messageContentTemplate]="messageContentTemplate"
6543
+ [authorMessageTemplate]="authorMessageTemplate"
6544
+ [receiverMessageTemplate]="receiverMessageTemplate"
6545
+ [messageTemplate]="messageTemplate"
6546
+ [timestampTemplate]="timestampTemplate"
6547
+ [statusTemplate]="statusTemplate"
6548
+ [userStatusTemplate]="userStatusTemplate"
6549
+ [localization]="localizationText"
6550
+ [attachmentTemplate]="attachmentTemplate"
6551
+ [authorId]="authorId"
6552
+ (executeAction)="dispatchAction($event)"
6553
+ (resize)="anchor.scrollToBottom()"
6554
+ (navigate)="this.autoScroll = false"
6555
+ >
6556
+ </kendo-chat-message-list>
6557
+ </ng-template>
5382
6558
  </div>
5383
6559
  <kendo-message-box
5384
6560
  #messageBox
@@ -5396,9 +6572,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5396
6572
  (fileRemove)="fileRemove.emit($event)"
5397
6573
  >
5398
6574
  </kendo-message-box>
6575
+
6576
+ <kendo-contextmenu
6577
+ #messagesContextMenu
6578
+ [items]="contextMenuActions"
6579
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
6580
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
6581
+ (popupClose)="handleMenuClose($event)"
6582
+ (select)="onContextMenuAction($event.item.originalAction)"
6583
+ ></kendo-contextmenu>
5399
6584
  `,
5400
6585
  standalone: true,
5401
- imports: [LocalizedMessagesDirective$1, ScrollAnchorDirective, MessageListComponent, MessageBoxComponent, MessageReferenceComponent, AppBarComponent, NgTemplateOutlet, NgIf, IconWrapperComponent, KENDO_BUTTON]
6586
+ imports: [LocalizedMessagesDirective$1, ScrollAnchorDirective, MessageListComponent, MessageBoxComponent, MessageReferenceComponent, AppBarComponent, NgTemplateOutlet, NgIf, IconWrapperComponent, KENDO_BUTTON, ContextMenuComponent]
5402
6587
  }]
5403
6588
  }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: ChatService }], propDecorators: { messages: [{
5404
6589
  type: Input
@@ -5424,13 +6609,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5424
6609
  type: Input
5425
6610
  }], inputValue: [{
5426
6611
  type: Input
6612
+ }], authorMessageSettings: [{
6613
+ type: Input
6614
+ }], receiverMessageSettings: [{
6615
+ type: Input
5427
6616
  }], messageContextMenuActions: [{
5428
6617
  type: Input
5429
6618
  }], fileActions: [{
5430
6619
  type: Input
5431
- }], suggestions: [{
6620
+ }], messageFilesLayout: [{
6621
+ type: Input
6622
+ }], suggestionsLayout: [{
5432
6623
  type: Input
5433
- }], messageToolbarVisibility: [{
6624
+ }], quickActionsLayout: [{
6625
+ type: Input
6626
+ }], suggestions: [{
5434
6627
  type: Input
5435
6628
  }], sendButtonSettings: [{
5436
6629
  type: Input
@@ -5464,12 +6657,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5464
6657
  }], dirAttr: [{
5465
6658
  type: HostBinding,
5466
6659
  args: ['attr.dir']
6660
+ }], messagesContextMenu: [{
6661
+ type: ViewChild,
6662
+ args: ['messagesContextMenu']
5467
6663
  }], attachmentTemplate: [{
5468
6664
  type: ContentChild,
5469
6665
  args: [AttachmentTemplateDirective]
5470
6666
  }], chatHeaderTemplate: [{
5471
6667
  type: ContentChild,
5472
6668
  args: [ChatHeaderTemplateDirective]
6669
+ }], chatNoDataTemplate: [{
6670
+ type: ContentChild,
6671
+ args: [NoDataTemplateDirective]
6672
+ }], authorMessageContentTemplate: [{
6673
+ type: ContentChild,
6674
+ args: [AuthorMessageContentTemplateDirective]
6675
+ }], receiverMessageContentTemplate: [{
6676
+ type: ContentChild,
6677
+ args: [ReceiverMessageContentTemplateDirective]
6678
+ }], messageContentTemplate: [{
6679
+ type: ContentChild,
6680
+ args: [MessageContentTemplateDirective]
6681
+ }], authorMessageTemplate: [{
6682
+ type: ContentChild,
6683
+ args: [AuthorMessageTemplateDirective]
6684
+ }], receiverMessageTemplate: [{
6685
+ type: ContentChild,
6686
+ args: [ReceiverMessageTemplateDirective]
5473
6687
  }], messageTemplate: [{
5474
6688
  type: ContentChild,
5475
6689
  args: [MessageTemplateDirective]
@@ -5485,6 +6699,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5485
6699
  }], messageBoxTemplate: [{
5486
6700
  type: ContentChild,
5487
6701
  args: [ChatMessageBoxTemplateDirective]
6702
+ }], userStatusTemplate: [{
6703
+ type: ContentChild,
6704
+ args: [ChatUserStatusTemplateDirective]
5488
6705
  }], messageBox: [{
5489
6706
  type: ViewChild,
5490
6707
  args: ['messageBox']
@@ -6724,13 +7941,20 @@ const KENDO_CHAT = [
6724
7941
  ChatComponent,
6725
7942
  CustomMessagesComponent,
6726
7943
  AttachmentTemplateDirective,
7944
+ AuthorMessageContentTemplateDirective,
7945
+ ReceiverMessageContentTemplateDirective,
7946
+ MessageContentTemplateDirective,
7947
+ AuthorMessageTemplateDirective,
7948
+ ReceiverMessageTemplateDirective,
6727
7949
  MessageTemplateDirective,
6728
7950
  HeroCardComponent,
6729
7951
  ChatMessageBoxTemplateDirective,
6730
7952
  ChatHeaderTemplateDirective,
7953
+ NoDataTemplateDirective,
6731
7954
  ChatTimestampTemplateDirective,
6732
7955
  ChatStatusTemplateDirective,
6733
- ChatSuggestionTemplateDirective
7956
+ ChatSuggestionTemplateDirective,
7957
+ ChatUserStatusTemplateDirective
6734
7958
  ];
6735
7959
  /**
6736
7960
  * Utility array that contains all InlineAIPrompt related components and directives.
@@ -6837,7 +8061,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6837
8061
  */
6838
8062
  class ChatModule {
6839
8063
  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] });
8064
+ 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
8065
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, providers: [IconsService, ResizeBatchService], imports: [ChatComponent, HeroCardComponent] });
6842
8066
  }
6843
8067
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatModule, decorators: [{
@@ -6870,7 +8094,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6870
8094
  */
6871
8095
  class ConversationalUIModule {
6872
8096
  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] });
8097
+ 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
8098
  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
8099
  }
6876
8100
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationalUIModule, decorators: [{
@@ -7058,5 +8282,5 @@ class InlineAIPromptSettings {
7058
8282
  * Generated bundle index. Do not edit.
7059
8283
  */
7060
8284
 
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 };
8285
+ 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
8286