UNPKG

@progress/kendo-angular-conversational-ui

Version:

Kendo UI for Angular Conversational UI components

741 lines (738 loc) 33.7 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ChangeDetectorRef, Component, ElementRef, forwardRef, HostBinding, HostListener, Input } from '@angular/core'; import { ChatItem } from './chat-item'; import { MessageContentTemplateDirective } from './templates/message-content-template.directive'; import { IntlService } from '@progress/kendo-angular-intl'; import { KENDO_BUTTONS } from '@progress/kendo-angular-buttons'; import { NgClass, NgTemplateOutlet } from '@angular/common'; import { IconWrapperComponent } from '@progress/kendo-angular-icons'; import { ToolBarButtonComponent, ToolBarComponent } from '@progress/kendo-angular-toolbar'; import { chevronDownIcon, chevronUpIcon, downloadIcon } from '@progress/kendo-svg-icons'; import { ChatService } from './common/chat.service'; import { LocalizationService } from '@progress/kendo-angular-l10n'; import { URL_REGEX, FILE_ACTION_BTN_SELECTOR, DOWNLOAD_ALL_SELECTOR, MENU_ITEM_SELECTOR, transformActions } from './common/utils'; import { isAuthor } from './chat-view'; import { ChatFileComponent } from './chat-file.component'; import { MessageReferenceComponent } from './message-reference-content.component'; import { ChatStatusTemplateDirective } from './templates/status-template.directive'; import { isPresent, Keys, normalizeKeys } from '@progress/kendo-angular-common'; import { MessageTemplateDirective } from './templates/message-template.directive'; import { AuthorMessageContentTemplateDirective } from './templates/author-message-content-template.directive'; import { ReceiverMessageContentTemplateDirective } from './templates/receiver-message-content-template.directive'; import { ReceiverMessageTemplateDirective } from './templates/receiver-message-template.directive'; import { AuthorMessageTemplateDirective } from './templates/author-message-template.directive'; import { Subscription } from 'rxjs'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-intl"; import * as i2 from "./common/chat.service"; import * as i3 from "@progress/kendo-angular-l10n"; import * as i4 from "@progress/kendo-angular-buttons"; // eslint-disable no-forward-ref /** * @hidden */ export class MessageComponent extends ChatItem { element; intl; chatService; localization; cdr; set message(value) { this._message = value; } get message() { return this._message; } tabbable; authorMessageContentTemplate; receiverMessageContentTemplate; messageContentTemplate; authorMessageTemplate; receiverMessageTemplate; messageTemplate; statusTemplate; showMessageTime = true; authorId; cssClass = true; get removedClass() { return this.message.isDeleted; } onKeyDown(event) { if (this.message.isDeleted) { return; } if (!this.chatService.allowMessageCollapse) { return; } this.onExpandableKeydown(event); } selected; get tabIndex() { return this.tabbable ? '0' : '-1'; } expandIcon = chevronDownIcon; collapseIcon = chevronUpIcon; downloadIcon = downloadIcon; isMessageExpanded = false; showExpandCollapseIcon = false; fileActions = []; toolbarActions = []; parts = []; get useCustomBubbleTemplate() { return !!(this.getActiveBubbleTemplate()); } get useCustomContentTemplate() { return !!(this.getActiveContentTemplate()); } get hasMessageContent() { return !!(this.message?.text || this.message?.files?.length > 0); } get hasFiles() { return this.message?.files?.length > 0; } get hasMultipleFiles() { return this.message?.files?.length > 1; } get isActiveMessage() { return this.chatService.active && this.message?.id === this.chatService.activeMessage?.id; } get isMessageExpandable() { const isOwn = this.isOwnMessage(this.message); const messageSettings = isOwn ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings; if (isPresent(messageSettings?.allowMessageCollapse)) { return messageSettings.allowMessageCollapse; } return this.chatService.allowMessageCollapse || false; } get showToolbar() { if (this.message?.isDeleted) { return false; } const hasComponentActions = this.chatService.messageToolbarActions?.length > 0; const hasMessageActions = this.toolbarActions?.length > 0; return hasComponentActions || hasMessageActions; } subs = new Subscription(); _message; constructor(element, intl, chatService, localization, cdr) { super(); this.element = element; this.intl = intl; this.chatService = chatService; this.localization = localization; this.cdr = cdr; } ngOnInit() { this.fileActions = this.getFileActions(); this.toolbarActions = this.getToolbarActions(); const settingsChange$ = this.isOwnMessage(this.message) ? this.chatService.authorMessageSettingsChange$ : this.chatService.receiverMessageSettingsChange$; this.subs.add(settingsChange$.subscribe(() => { this.fileActions = this.getFileActions(); this.toolbarActions = this.getToolbarActions(); setTimeout(() => { this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility(); }); })); this.subs.add(this.chatService.allowMessageCollapseChange$.subscribe(() => { setTimeout(() => { this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility(); }); })); if (this.message.id) { this.chatService.registerMessageElement(this.message.id, this.element); } this.parts = this.getFormattedTextParts(this.message.text); } ngAfterViewInit() { this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility(); this.cdr.detectChanges(); } ngOnDestroy() { if (this.message.id) { this.chatService.unregisterMessageElement(this.message.id); } this.subs.unsubscribe(); } calculateExpandCollapseIconVisibility() { if (this.isMessageExpanded) { return true; } const bubbleContent = this.element.nativeElement.querySelector('.k-bubble-content'); if (!bubbleContent) { return false; } const hasVerticalOverflow = bubbleContent.scrollHeight > bubbleContent.clientHeight; const hasHorizontalOverflow = bubbleContent.scrollWidth > bubbleContent.clientWidth; if (this.useCustomContentTemplate) { return hasVerticalOverflow || hasHorizontalOverflow; } const messageText = this.element.nativeElement.querySelector('.k-chat-bubble-text'); const hasTextOverflow = messageText?.scrollWidth > messageText?.clientWidth; return hasTextOverflow || hasVerticalOverflow || hasHorizontalOverflow; } getActiveBubbleTemplate() { const isOwn = this.isOwnMessage(this.message); if (isOwn && this.authorMessageTemplate) { return this.authorMessageTemplate; } if (!isOwn && this.receiverMessageTemplate) { return this.receiverMessageTemplate; } if (this.messageTemplate) { return this.messageTemplate; } return null; } getActiveContentTemplate() { const isOwn = this.isOwnMessage(this.message); if (isOwn && this.authorMessageContentTemplate) { return this.authorMessageContentTemplate; } if (!isOwn && this.receiverMessageContentTemplate) { return this.receiverMessageContentTemplate; } if (this.messageContentTemplate) { return this.messageContentTemplate; } return null; } getDeletedMessageText() { const isOwn = this.isOwnMessage(this.message); return isOwn ? this.textFor('deletedMessageSenderText') : this.textFor('deletedMessageReceiverText'); } textFor(key) { return this.localization.get(key); } formatTimeStamp(date) { return this.intl.formatDate(date, { datetime: 'short' }); } focus() { this.element.nativeElement.focus(); } onDownloadAll() { this.chatService.emit('fileDownload', { files: this.message.files, message: this.message }); } toggleMessageState(event) { event.stopImmediatePropagation(); this.isMessageExpanded = !this.isMessageExpanded; this.chatService.toggleMessageState = false; } onExpandableKeydown(event) { const key = normalizeKeys(event); const isFileActionButton = event.target.closest(FILE_ACTION_BTN_SELECTOR) || event.target.closest(DOWNLOAD_ALL_SELECTOR); if (!isFileActionButton && (key === Keys.Enter || key === Keys.Space)) { event.preventDefault(); this.chatService.toggleMessageState = true; this.toggleMessageState(event); } } onToolbarAction(event, action, message) { event.stopImmediatePropagation(); this.chatService.emit('toolbarAction', { action, message }); } onFileAction(action, file) { if (action.originalAction.id === 'download') { this.chatService.emit('fileDownload', { files: [file], message: this.message }); } this.chatService.emit('fileAction', { action: action.originalAction, file }); } getMessageById(id) { return this.chatService.getMessageById(id); } onReplyReferenceClick(event, replyToId) { event.stopPropagation(); this.chatService.emit('replyReferenceClick', replyToId); } handleMenuClose(event) { if (event) { const originalEvent = event.originalEvent; if (originalEvent) { this.onActionButtonClick(originalEvent); } } this.chatService.active = false; this.chatService.emit('contextMenuVisibilityChange', false); if (this.chatService.selectOnMenuClose) { this.selected = true; this.focus(); } } onActionButtonClick(event) { const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble'); const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR); if (clickOutsideMessage && !menuItemClick) { this.chatService.selectOnMenuClose = false; } } handleMenuOpen() { this.chatService.selectOnMenuClose = this.selected; this.chatService.emit('contextMenuVisibilityChange', true); } onActionPopupChange(expanded) { if (expanded) { this.chatService.active = true; this.handleMenuOpen(); } else { this.handleMenuClose(); } } isOwnMessage(msg) { return isAuthor(this.authorId, msg); } getFormattedTextParts(text) { if (!text) { return []; } const parts = []; const urlMatches = Array.from(text.matchAll(URL_REGEX)); let lastIndex = 0; for (const match of urlMatches) { const url = match[1]; const matchStart = match.index; if (!isPresent(matchStart)) { continue; } if (matchStart > lastIndex) { parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) }); } parts.push({ type: 'link', content: url, href: url }); lastIndex = matchStart + match[0].length; } if (lastIndex < text.length) { parts.push({ type: 'text', content: text.substring(lastIndex) }); } return parts; } getMessageSettings() { return this.isOwnMessage(this.message) ? this.chatService.authorMessageSettings : this.chatService.receiverMessageSettings; } getToolbarActions() { const messageSettings = this.getMessageSettings(); return messageSettings?.messageToolbarActions?.length ? messageSettings.messageToolbarActions : this.chatService.messageToolbarActions || []; } getFileActions() { const messageSettings = this.getMessageSettings(); const actions = messageSettings?.fileActions?.length ? messageSettings.fileActions : this.chatService.fileActions || []; return transformActions(actions); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }, { token: i2.ChatService }, { token: i3.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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: [ { provide: ChatItem, useExisting: forwardRef(() => MessageComponent) } ], usesInheritance: true, ngImport: i0, template: ` @if (useCustomBubbleTemplate) { <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container> } @if (!useCustomBubbleTemplate) { @if (chatService.timestampVisibility === 'focus' && message.timestamp) { <time [attr.aria-hidden]="!selected" class="k-message-time" > {{ formatTimeStamp(message.timestamp) }} </time> } @if (message.typing) { <div class="k-chat-bubble k-bubble"> <div class="k-typing-indicator" [attr.tabindex]="'-1'"> <span></span> <span></span> <span></span> </div> </div> } @if (!message.typing) { @if (useCustomContentTemplate) { <div class="k-chat-bubble k-bubble" [attr.tabindex]="0" [ngClass]="{ 'k-bubble-expandable': isMessageExpandable, 'k-expanded': isMessageExpanded, 'k-selected': selected, 'k-focus': selected, 'k-active': isActiveMessage }" > <div class="k-bubble-content"> <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container> </div> @if (isMessageExpandable && showExpandCollapseIcon) { <span class="k-bubble-expandable-indicator" [attr.tabindex]="'0'" [attr.role]="'button'" [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')" (mousedown)="chatService.toggleMessageState = true" (click)="toggleMessageState($event)" > <kendo-icon-wrapper [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon" > </kendo-icon-wrapper> </span> } </div> } @if (!useCustomContentTemplate && hasMessageContent) { <div class="k-chat-bubble k-bubble" [attr.tabindex]="0" [ngClass]="{ 'k-bubble-expandable': isMessageExpandable, 'k-expanded': isMessageExpanded, 'k-selected': selected, 'k-focus': selected, 'k-active': isActiveMessage }" > <div class="k-bubble-content"> @if (message.text || message.isDeleted) { @if (message.replyToId && !message.isDeleted) { <div class="k-message-reference k-message-reference-receiver" (click)="onReplyReferenceClick($event, message.replyToId)" > <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content> </div> } @if (message.isDeleted) { <span class="k-chat-bubble-text"> {{ getDeletedMessageText() }} </span> } @if (!message.isDeleted && parts.length > 0) { <span class="k-chat-bubble-text"> @for (part of parts; track part) { @if (part.type === 'text') {{{part.content}}} @if (part.type === 'link') {<a [href]="part.href" target="_blank">{{part.content}}</a>} } </span> } } @if (hasFiles && !message.isDeleted) { <ul class="k-chat-file-wrapper" [ngClass]="{ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap', 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal' }" > @for (file of message.files; track file) { <li class="k-chat-file" [chatFile]="file" [fileActions]="fileActions" (actionClick)="onFileAction($event, file)" (actionsToggle)="onActionPopupChange($event)" (actionButtonClick)="onActionButtonClick($event)" ></li> } </ul> } @if (hasMultipleFiles && !message.isDeleted) { <div class="k-chat-download-button-wrapper"> <button kendoButton class="k-chat-download-button" fillMode="flat" icon="download" [svgIcon]="downloadIcon" [attr.title]="textFor('downloadAllFilesText')" (click)="onDownloadAll()" >{{ textFor('downloadAllFilesText') }}</button> </div> } </div> @if (isMessageExpandable && showExpandCollapseIcon) { <span class="k-bubble-expandable-indicator" [attr.tabindex]="'0'" [attr.role]="'button'" [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')" (mousedown)="chatService.toggleMessageState = true" (click)="toggleMessageState($event)" > <kendo-icon-wrapper [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon" > </kendo-icon-wrapper> </span> } </div> } } @if (message.status) { <span class="k-message-status"> @if (statusTemplate?.templateRef) { <ng-template [ngTemplateOutlet]="statusTemplate.templateRef" [ngTemplateOutletContext]="{ $implicit: message.status, message }" > </ng-template> } @if (!statusTemplate?.templateRef) { {{ message.status }} } </span> } } @if (showToolbar) { <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat"> @for (action of toolbarActions; track action) { <kendo-toolbar-button fillMode="flat" [icon]="action.icon" [svgIcon]="action.svgIcon" [disabled]="action.disabled" [title]="action.label" (click)="onToolbarAction($event, action, message)" > </kendo-toolbar-button> } </kendo-toolbar> } `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: i4.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"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-chat-message', providers: [ { provide: ChatItem, useExisting: forwardRef(() => MessageComponent) } ], template: ` @if (useCustomBubbleTemplate) { <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container> } @if (!useCustomBubbleTemplate) { @if (chatService.timestampVisibility === 'focus' && message.timestamp) { <time [attr.aria-hidden]="!selected" class="k-message-time" > {{ formatTimeStamp(message.timestamp) }} </time> } @if (message.typing) { <div class="k-chat-bubble k-bubble"> <div class="k-typing-indicator" [attr.tabindex]="'-1'"> <span></span> <span></span> <span></span> </div> </div> } @if (!message.typing) { @if (useCustomContentTemplate) { <div class="k-chat-bubble k-bubble" [attr.tabindex]="0" [ngClass]="{ 'k-bubble-expandable': isMessageExpandable, 'k-expanded': isMessageExpanded, 'k-selected': selected, 'k-focus': selected, 'k-active': isActiveMessage }" > <div class="k-bubble-content"> <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container> </div> @if (isMessageExpandable && showExpandCollapseIcon) { <span class="k-bubble-expandable-indicator" [attr.tabindex]="'0'" [attr.role]="'button'" [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')" (mousedown)="chatService.toggleMessageState = true" (click)="toggleMessageState($event)" > <kendo-icon-wrapper [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon" > </kendo-icon-wrapper> </span> } </div> } @if (!useCustomContentTemplate && hasMessageContent) { <div class="k-chat-bubble k-bubble" [attr.tabindex]="0" [ngClass]="{ 'k-bubble-expandable': isMessageExpandable, 'k-expanded': isMessageExpanded, 'k-selected': selected, 'k-focus': selected, 'k-active': isActiveMessage }" > <div class="k-bubble-content"> @if (message.text || message.isDeleted) { @if (message.replyToId && !message.isDeleted) { <div class="k-message-reference k-message-reference-receiver" (click)="onReplyReferenceClick($event, message.replyToId)" > <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content> </div> } @if (message.isDeleted) { <span class="k-chat-bubble-text"> {{ getDeletedMessageText() }} </span> } @if (!message.isDeleted && parts.length > 0) { <span class="k-chat-bubble-text"> @for (part of parts; track part) { @if (part.type === 'text') {{{part.content}}} @if (part.type === 'link') {<a [href]="part.href" target="_blank">{{part.content}}</a>} } </span> } } @if (hasFiles && !message.isDeleted) { <ul class="k-chat-file-wrapper" [ngClass]="{ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap', 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal' }" > @for (file of message.files; track file) { <li class="k-chat-file" [chatFile]="file" [fileActions]="fileActions" (actionClick)="onFileAction($event, file)" (actionsToggle)="onActionPopupChange($event)" (actionButtonClick)="onActionButtonClick($event)" ></li> } </ul> } @if (hasMultipleFiles && !message.isDeleted) { <div class="k-chat-download-button-wrapper"> <button kendoButton class="k-chat-download-button" fillMode="flat" icon="download" [svgIcon]="downloadIcon" [attr.title]="textFor('downloadAllFilesText')" (click)="onDownloadAll()" >{{ textFor('downloadAllFilesText') }}</button> </div> } </div> @if (isMessageExpandable && showExpandCollapseIcon) { <span class="k-bubble-expandable-indicator" [attr.tabindex]="'0'" [attr.role]="'button'" [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')" (mousedown)="chatService.toggleMessageState = true" (click)="toggleMessageState($event)" > <kendo-icon-wrapper [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon" > </kendo-icon-wrapper> </span> } </div> } } @if (message.status) { <span class="k-message-status"> @if (statusTemplate?.templateRef) { <ng-template [ngTemplateOutlet]="statusTemplate.templateRef" [ngTemplateOutletContext]="{ $implicit: message.status, message }" > </ng-template> } @if (!statusTemplate?.templateRef) { {{ message.status }} } </span> } } @if (showToolbar) { <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat"> @for (action of toolbarActions; track action) { <kendo-toolbar-button fillMode="flat" [icon]="action.icon" [svgIcon]="action.svgIcon" [disabled]="action.disabled" [title]="action.label" (click)="onToolbarAction($event, action, message)" > </kendo-toolbar-button> } </kendo-toolbar> } `, standalone: true, imports: [NgClass, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent], }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.IntlService }, { type: i2.ChatService }, { type: i3.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { message: [{ type: Input }], tabbable: [{ type: Input }], authorMessageContentTemplate: [{ type: Input }], receiverMessageContentTemplate: [{ type: Input }], messageContentTemplate: [{ type: Input }], authorMessageTemplate: [{ type: Input }], receiverMessageTemplate: [{ type: Input }], messageTemplate: [{ type: Input }], statusTemplate: [{ type: Input }], showMessageTime: [{ type: Input }], authorId: [{ type: Input }], cssClass: [{ type: HostBinding, args: ['class.k-message'] }], removedClass: [{ type: HostBinding, args: ['class.k-message-removed'] }], onKeyDown: [{ type: HostListener, args: ['keydown', ['$event']] }], tabIndex: [{ type: HostBinding, args: ['attr.tabIndex'] }] } });