@progress/kendo-angular-conversational-ui
Version:
Kendo UI for Angular Conversational UI components
1,277 lines (1,246 loc) • 148 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import * as i0 from '@angular/core';
import { Directive, Optional, EventEmitter, Component, HostBinding, ViewChild, Input, Output, isDevMode, forwardRef, ViewChildren, ElementRef, ContentChild, InjectionToken, Inject, Injectable, ContentChildren, NgModule } from '@angular/core';
import * as i1$1 from '@progress/kendo-angular-l10n';
import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import { validatePackage } from '@progress/kendo-licensing';
import { NgIf, NgTemplateOutlet, NgFor, NgSwitch, NgSwitchCase } from '@angular/common';
import { Keys, ResizeSensorComponent, isPresent, focusableSelector, guid, ResizeBatchService } from '@progress/kendo-angular-common';
import { paperPlaneIcon, chevronLeftIcon, chevronRightIcon, sparklesIcon, commentIcon, moreHorizontalIcon, thumbUpIcon, thumbDownOutlineIcon, thumbDownIcon, thumbUpOutlineIcon, copyIcon, arrowRotateCwIcon, chevronUpIcon, chevronDownIcon } from '@progress/kendo-svg-icons';
import { ButtonComponent } from '@progress/kendo-angular-buttons';
import { TextBoxComponent, TextAreaComponent, InputSeparatorComponent, TextBoxSuffixTemplateDirective, TextAreaSuffixComponent } from '@progress/kendo-angular-inputs';
import * as i1 from '@progress/kendo-angular-intl';
import { fromEvent, Subscription, Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { IconsService } from '@progress/kendo-angular-icons';
import { PopupService } from '@progress/kendo-angular-popup';
import { DialogContainerService, DialogService, WindowService, WindowContainerService } from '@progress/kendo-angular-dialog';
import { PanelBarComponent } from '@progress/kendo-angular-layout';
/**
* Defines a template for displaying message attachments.
*
* To define an attachment template, nest an `<ng-template>` tag with the `kendoChatAttachmentTemplate` attribute inside the `<kendo-chat>` component.
* The template context is set to the attachment instance.
* For more information, refer to the article on [message attachments](slug:attachments_chat).
*
* @example
* ```html
* <kendo-chat>
* <ng-template kendoChatAttachmentTemplate let-attachment>
* <div>Attachment: {{ attachment.content }}</div>
* </ng-template>
* </kendo-chat>
* ```
*/
class AttachmentTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AttachmentTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: AttachmentTemplateDirective, isStandalone: true, selector: "[kendoChatAttachmentTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AttachmentTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoChatAttachmentTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{
type: Optional
}] }]; } });
/**
* Describes the arguments for the `sendMessage` event.
*/
class SendMessageEvent {
/**
* Represents the message that contains the metadata and user input.
*
* > The Chat does not automatically add the message to its data.
* > For more information, see [Data Binding](slug:databinding_chat).
*/
message;
/**
* @hidden
*/
constructor(message) {
this.message = message;
}
}
const noop = () => { };
const handlers = {
'reply': (action, sender) => {
sender.sendMessage.emit(new SendMessageEvent({
author: sender.user,
text: action.value,
timestamp: new Date()
}));
},
'call': (action) => {
window.open('tel:' + action.value);
},
'openUrl': (action) => {
window.open(action.value);
}
};
/**
* @hidden
*/
const makeHandler = (action) => handlers[action.type] || noop;
/**
* Defines a template for displaying Chat messages.
*
* To define a message template, nest an `<ng-template>` tag with the `kendoChatMessageTemplate` directive inside the `<kendo-chat>` component.
* The template context is set to the `message` instance.
* For more information, refer to the article on [message templates](slug:message_templates_chat).
*
* @example
* ```html
* <kendo-chat>
* <ng-template kendoChatMessageTemplate let-message>
* <div>Message: {{ message.text }}</div>
* </ng-template>
* </kendo-chat>
* ```
*/
class MessageTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: MessageTemplateDirective, isStandalone: true, selector: "[kendoChatMessageTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoChatMessageTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{
type: Optional
}] }]; } });
/**
* @hidden
*/
const packageMetadata = {
name: '@progress/kendo-angular-conversational-ui',
productName: 'Kendo UI for Angular',
productCode: 'KENDOUIANGULAR',
productCodes: ['KENDOUIANGULAR'],
publishDate: 1751463370,
version: '19.2.0',
licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/'
};
/**
* Creates a message box area that overrides the default message box of the Chat component.
*
* To define a message box template, nest an `<ng-template>` tag with the `kendoChatMessageBoxTemplate` directive inside the `<kendo-chat>` tag.
* For more information, see [Message Box Template](slug:message_box#toc-message-box-template).
*
* @example
* ```html
* <kendo-chat>
* <ng-template kendoChatMessageBoxTemplate>
* <input type="text" placeholder="Custom message box..." />
* </ng-template>
* </kendo-chat>
* ```
*/
class ChatMessageBoxTemplateDirective {
templateRef;
constructor(templateRef) {
this.templateRef = templateRef;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChatMessageBoxTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ChatMessageBoxTemplateDirective, isStandalone: true, selector: "[kendoChatMessageBoxTemplate]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChatMessageBoxTemplateDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoChatMessageBoxTemplate]',
standalone: true
}]
}], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } });
/**
* @hidden
*/
class MessageBoxComponent {
borderColor = 'inherit';
messageBoxInput;
user;
autoScroll;
type;
localization;
messageBoxTemplate;
sendMessage = new EventEmitter();
/**
* @hidden
*/
sendIcon = paperPlaneIcon;
/**
* @hidden
*/
sendClick() {
const input = this.messageBoxInput;
const value = input.value;
if (!value) {
return;
}
const message = {
author: this.user,
text: value,
timestamp: new Date()
};
this.sendMessage.emit(new SendMessageEvent(message));
input.value = null;
input.focus();
this.autoScroll = true;
}
/**
* @hidden
*/
inputKeydown(e) {
if (e.keyCode === Keys.Enter) {
this.sendClick();
}
}
/**
* @hidden
*/
textAreaKeydown(e) {
const isEnter = e.keyCode === Keys.Enter;
if (!isEnter) {
return;
}
const newLine = (e.metaKey || e.ctrlKey);
const enterOnly = !(e.shiftKey || e.metaKey || e.ctrlKey);
if (enterOnly) {
e.preventDefault();
this.sendClick();
}
if (newLine) {
this.messageBoxInput.value += `\r\n`;
}
}
/**
* @hidden
*/
textFor(key) {
return this.localization.get(key);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MessageBoxComponent, isStandalone: true, selector: "kendo-message-box", inputs: { user: "user", autoScroll: "autoScroll", type: "type", localization: "localization", messageBoxTemplate: "messageBoxTemplate" }, outputs: { sendMessage: "sendMessage" }, host: { properties: { "style.border-color": "this.borderColor" } }, viewQueries: [{ propertyName: "messageBoxInput", first: true, predicate: ["messageBoxInput"], descendants: true }], ngImport: i0, template: `
<ng-container *ngIf="!messageBoxTemplate">
<kendo-textbox
*ngIf="type === 'textbox'"
#messageBoxInput
class="k-message-box"
[inputAttributes]="{
'aria-label': textFor('messageBoxInputLabel')
}"
[placeholder]="textFor('messagePlaceholder')"
(keydown)="inputKeydown($event)"
>
<ng-template kendoTextBoxSuffixTemplate>
<kendo-textbox-separator></kendo-textbox-separator>
<button
kendoButton
fillMode="flat"
class="k-chat-send"
icon="paper-plane"
[svgIcon]="sendIcon"
[tabindex]="0"
[attr.title]="textFor('send')"
(click)="sendClick()"
>
</button>
</ng-template>
</kendo-textbox>
<kendo-textarea
*ngIf="type === 'textarea'"
#messageBoxInput
class="k-message-box !k-align-items-end"
resizable="none"
[rows]="3"
[inputAttributes]="{
'aria-label': textFor('messageBoxInputLabel')
}"
[placeholder]="textFor('messagePlaceholder')"
[showSuffixSeparator]="true"
(keydown)="textAreaKeydown($event)"
>
<kendo-textarea-suffix>
<button
kendoButton
fillMode="flat"
class="k-chat-send"
icon="paper-plane"
[svgIcon]="sendIcon"
[tabindex]="0"
[attr.title]="textFor('send')"
(click)="sendClick()"
>
</button>
</kendo-textarea-suffix>
</kendo-textarea>
</ng-container>
<ng-template *ngIf="messageBoxTemplate" [ngTemplateOutlet]="messageBoxTemplate?.templateRef"></ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: InputSeparatorComponent, selector: "kendo-input-separator, kendo-textbox-separator", inputs: ["orientation"] }, { kind: "directive", type: TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "component", type: TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageBoxComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-message-box',
template: `
<ng-container *ngIf="!messageBoxTemplate">
<kendo-textbox
*ngIf="type === 'textbox'"
#messageBoxInput
class="k-message-box"
[inputAttributes]="{
'aria-label': textFor('messageBoxInputLabel')
}"
[placeholder]="textFor('messagePlaceholder')"
(keydown)="inputKeydown($event)"
>
<ng-template kendoTextBoxSuffixTemplate>
<kendo-textbox-separator></kendo-textbox-separator>
<button
kendoButton
fillMode="flat"
class="k-chat-send"
icon="paper-plane"
[svgIcon]="sendIcon"
[tabindex]="0"
[attr.title]="textFor('send')"
(click)="sendClick()"
>
</button>
</ng-template>
</kendo-textbox>
<kendo-textarea
*ngIf="type === 'textarea'"
#messageBoxInput
class="k-message-box !k-align-items-end"
resizable="none"
[rows]="3"
[inputAttributes]="{
'aria-label': textFor('messageBoxInputLabel')
}"
[placeholder]="textFor('messagePlaceholder')"
[showSuffixSeparator]="true"
(keydown)="textAreaKeydown($event)"
>
<kendo-textarea-suffix>
<button
kendoButton
fillMode="flat"
class="k-chat-send"
icon="paper-plane"
[svgIcon]="sendIcon"
[tabindex]="0"
[attr.title]="textFor('send')"
(click)="sendClick()"
>
</button>
</kendo-textarea-suffix>
</kendo-textarea>
</ng-container>
<ng-template *ngIf="messageBoxTemplate" [ngTemplateOutlet]="messageBoxTemplate?.templateRef"></ng-template>
`,
standalone: true,
imports: [NgIf, ButtonComponent, NgTemplateOutlet, TextBoxComponent, TextAreaComponent, InputSeparatorComponent, TextBoxSuffixTemplateDirective, TextAreaSuffixComponent]
}]
}], propDecorators: { borderColor: [{
type: HostBinding,
args: ['style.border-color']
}], messageBoxInput: [{
type: ViewChild,
args: ['messageBoxInput', { static: false }]
}], user: [{
type: Input
}], autoScroll: [{
type: Input
}], type: [{
type: Input
}], localization: [{
type: Input
}], messageBoxTemplate: [{
type: Input
}], sendMessage: [{
type: Output
}] } });
/**
* @hidden
*/
class PreventableEvent {
prevented = false;
/**
* Prevents the default action for a specified event.
* In this way, the source component suppresses
* the built-in behavior that follows the event.
*/
preventDefault() {
this.prevented = true;
}
/**
* Returns `true` if the event was prevented
* by any of its subscribers.
*
* @returns `true` if the default action was prevented.
* Otherwise, returns `false`.
*/
isDefaultPrevented() {
return this.prevented;
}
}
/**
* Represents the arguments for the `executeAction` event.
*
* The `executeAction` event fires when the user clicks a quick action button.
* Call `preventDefault()` to suppress the built-in action handler.
*/
class ExecuteActionEvent extends PreventableEvent {
/**
* Represents the action to execute.
*/
action;
/**
* Represents the message that contains the action.
*/
message;
/**
* @hidden
*/
constructor(action, message) {
super();
this.action = action;
this.message = message;
}
}
/**
* @hidden
*/
const closest = (node, predicate) => {
while (node && !predicate(node)) {
node = node.parentNode;
}
return node;
};
/**
* @hidden
*/
class ChatItem {
selected;
}
/**
* @hidden
*/
const isAuthor = (user, msg) => user && msg.author && user.id === msg.author.id;
const last = (arr) => arr[arr.length - 1];
const dateChanged = (curr, prev) => (curr && prev) && (prev.getDate() !== curr.getDate() ||
prev.getMonth() !== curr.getMonth() ||
prev.getFullYear() !== curr.getFullYear());
const addDateMarker = (acc, msg) => {
const timestamp = msg.timestamp;
const lastItem = last(acc);
if (!timestamp) {
return;
}
if (!lastItem || dateChanged(timestamp, lastItem.timestamp)) {
const dateMarker = {
type: 'date-marker',
timestamp: timestamp,
trackBy: timestamp.getTime()
};
acc.push(dateMarker);
}
};
const groupMessages = (acc, msg, isLastMessage) => {
const lastItem = last(acc);
let messages;
if (isDevMode() && !msg.author) {
throw new Error('Author must be set for message: ' + JSON.stringify(msg));
}
if (msg.typing && !isLastMessage) {
return;
}
if (lastItem && lastItem.type === 'message-group') {
messages = lastItem.messages;
}
if (messages && isAuthor(msg.author, last(messages))) {
messages.push(msg);
}
else {
acc.push({
type: 'message-group',
messages: [msg],
author: msg.author,
timestamp: msg.timestamp,
trackBy: msg
});
}
};
const groupItems = (total) => (acc, msg, index) => {
const isLastMessage = index === total - 1;
addDateMarker(acc, msg);
groupMessages(acc, msg, isLastMessage);
if (msg.attachments && msg.attachments.length > 1) {
acc.push({
type: 'attachment-group',
attachments: msg.attachments,
attachmentLayout: msg.attachmentLayout,
timestamp: msg.timestamp,
trackBy: msg
});
}
if (msg.suggestedActions && isLastMessage) {
acc.push({
type: 'action-group',
actions: msg.suggestedActions,
timestamp: msg.timestamp,
trackBy: msg
});
}
return acc;
};
/**
* @hidden
*/
const chatView = (messages) => messages.reduce(groupItems(messages.length), []);
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-empty-function */
/**
* @hidden
*/
class SuggestedActionsComponent extends ChatItem {
actions;
tabbable;
dispatch = new EventEmitter();
defaultClass = true;
items;
selectedIndex = 0;
keyHandlers = {
[Keys.Tab]: (e) => this.changeSelectedIndex(e),
[Keys.Enter]: (_, action) => this.actionClick(action),
[Keys.Space]: (_, action) => this.actionClick(action),
};
isSelected(index) {
return this.selected && this.selectedIndex === index;
}
actionClick(action) {
this.dispatch.next(action);
}
actionKeydown(e, action) {
const handler = this.keyHandlers[e.keyCode];
if (handler) {
handler(e, action);
}
}
focus() { }
changeSelectedIndex(e) {
const offset = e.shiftKey ? -1 : 1;
const prevIndex = this.selectedIndex;
this.selectedIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SuggestedActionsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SuggestedActionsComponent, isStandalone: true, selector: "kendo-chat-suggested-actions", inputs: { actions: "actions", tabbable: "tabbable" }, outputs: { dispatch: "dispatch" }, host: { properties: { "class.k-quick-replies": "this.defaultClass" } }, providers: [{
provide: ChatItem,
useExisting: forwardRef(() => SuggestedActionsComponent)
}], viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
<span
#item
*ngFor="let action of actions; index as index; first as first; last as last"
class="k-quick-reply"
role="button"
[class.k-selected]="isSelected(index)"
[class.k-focus]="isSelected(index)"
[class.k-first]="first"
[class.k-last]="last"
[attr.tabindex]="0"
(click)="actionClick(action)"
(keydown)="actionKeydown($event, action)"
>
{{ action.title || action.value }}
</span>
`, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SuggestedActionsComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-chat-suggested-actions',
providers: [{
provide: ChatItem,
useExisting: forwardRef(() => SuggestedActionsComponent)
}],
template: `
<span
#item
*ngFor="let action of actions; index as index; first as first; last as last"
class="k-quick-reply"
role="button"
[class.k-selected]="isSelected(index)"
[class.k-focus]="isSelected(index)"
[class.k-first]="first"
[class.k-last]="last"
[attr.tabindex]="0"
(click)="actionClick(action)"
(keydown)="actionKeydown($event, action)"
>
{{ action.title || action.value }}
</span>
`,
standalone: true,
imports: [NgFor]
}]
}], propDecorators: { actions: [{
type: Input
}], tabbable: [{
type: Input
}], dispatch: [{
type: Output
}], defaultClass: [{
type: HostBinding,
args: ['class.k-quick-replies']
}], items: [{
type: ViewChildren,
args: ['item']
}] } });
// eslint-disable no-forward-ref
/**
* @hidden
*/
class MessageComponent extends ChatItem {
element;
intl;
message;
tabbable;
template;
cssClass = true;
selected;
get tabIndex() {
return this.tabbable ? '0' : '-1';
}
constructor(element, intl) {
super();
this.element = element;
this.intl = intl;
}
formatTimeStamp(date) {
return this.intl.formatDate(date, { datetime: 'short' });
}
focus() {
this.element.nativeElement.focus();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", template: "template" }, host: { properties: { "class.k-message": "this.cssClass", "class.k-selected": "this.selected", "class.k-focus": "this.selected", "attr.tabIndex": "this.tabIndex" } }, providers: [{
provide: ChatItem,
useExisting: forwardRef(() => MessageComponent)
}], usesInheritance: true, ngImport: i0, template: `
<time
[attr.aria-hidden]="!selected"
class="k-message-time"
*ngIf="message.timestamp"
>
{{ formatTimeStamp(message.timestamp) }}
</time>
<ng-container *ngIf="!message.typing; else typing">
<div class="k-chat-bubble" *ngIf="template">
<ng-container
*ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
>
</ng-container>
</div>
<div class="k-chat-bubble" *ngIf="!template && message.text">
{{message.text}}
</div>
</ng-container>
<span
class="k-message-status"
*ngIf="message.status"
>
{{ message.status }}
</span>
<ng-template #typing>
<div class="k-chat-bubble">
<div class="k-typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-chat-message',
providers: [{
provide: ChatItem,
useExisting: forwardRef(() => MessageComponent)
}],
template: `
<time
[attr.aria-hidden]="!selected"
class="k-message-time"
*ngIf="message.timestamp"
>
{{ formatTimeStamp(message.timestamp) }}
</time>
<ng-container *ngIf="!message.typing; else typing">
<div class="k-chat-bubble" *ngIf="template">
<ng-container
*ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
>
</ng-container>
</div>
<div class="k-chat-bubble" *ngIf="!template && message.text">
{{message.text}}
</div>
</ng-container>
<span
class="k-message-status"
*ngIf="message.status"
>
{{ message.status }}
</span>
<ng-template #typing>
<div class="k-chat-bubble">
<div class="k-typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</ng-template>
`,
standalone: true,
imports: [NgIf, NgTemplateOutlet]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.IntlService }]; }, propDecorators: { message: [{
type: Input
}], tabbable: [{
type: Input
}], template: [{
type: Input
}], cssClass: [{
type: HostBinding,
args: ['class.k-message']
}], selected: [{
type: HostBinding,
args: ['class.k-selected']
}, {
type: HostBinding,
args: ['class.k-focus']
}], tabIndex: [{
type: HostBinding,
args: ['attr.tabIndex']
}] } });
/**
* @hidden
*/
class AttachmentComponent {
set attachment(value) {
this._attachment = value;
this.context = {
$implicit: this.attachment
};
}
get attachment() {
return this._attachment;
}
template;
get image() {
return this.contentType.startsWith('image/');
}
get unknown() {
return !this.image;
}
context;
get contentType() {
return this.attachment.contentType || '';
}
_attachment;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AttachmentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AttachmentComponent, isStandalone: true, selector: "kendo-chat-attachment", inputs: { attachment: "attachment", template: "template" }, ngImport: i0, template: `
<ng-container *ngIf="template">
<ng-container *ngTemplateOutlet="template.templateRef; context: context;">
</ng-container>
</ng-container>
<div *ngIf="!template" class="k-card">
<div class="k-card-body">
<h5 class="k-card-title" *ngIf="attachment.title">
{{ attachment.title }}
</h5>
<h6 class="k-card-subtitle" *ngIf="attachment.subtitle">
{{ attachment.subtitle }}
</h6>
<img *ngIf="image" [attr.src]="attachment.content" />
<ng-container *ngIf="unknown">
{{ attachment.content }}
</ng-container>
</div>
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AttachmentComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-chat-attachment',
template: `
<ng-container *ngIf="template">
<ng-container *ngTemplateOutlet="template.templateRef; context: context;">
</ng-container>
</ng-container>
<div *ngIf="!template" class="k-card">
<div class="k-card-body">
<h5 class="k-card-title" *ngIf="attachment.title">
{{ attachment.title }}
</h5>
<h6 class="k-card-subtitle" *ngIf="attachment.subtitle">
{{ attachment.subtitle }}
</h6>
<img *ngIf="image" [attr.src]="attachment.content" />
<ng-container *ngIf="unknown">
{{ attachment.content }}
</ng-container>
</div>
</div>
`,
standalone: true,
imports: [NgIf, NgTemplateOutlet]
}]
}], propDecorators: { attachment: [{
type: Input
}], template: [{
type: Input
}] } });
/* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable no-forward-ref
/**
* @hidden
*/
class MessageAttachmentsComponent extends ChatItem {
zone;
localizationService;
/**
* @hidden
*/
chevronLeftIcon = chevronLeftIcon;
/**
* @hidden
*/
chevronRightIcon = chevronRightIcon;
attachments;
layout;
tabbable;
template;
localization;
get carousel() {
return this.layout !== 'list';
}
deck;
items;
scrollPosition = 0;
selectedIndex = 0;
scrollSubscription;
direction;
get showLeftArrow() {
return this.carousel && this.direction === 'rtl' ? this.scrollPosition > -1 : this.scrollPosition > 0;
}
get showRightArrow() {
return this.carousel && this.direction === 'rtl' ? this.scrollPosition < 0 : this.scrollPosition < 1;
}
carouselKeyHandlers = {
[Keys.ArrowLeft]: (e) => this.navigateTo(e, this.direction === 'rtl' ? 1 : -1),
[Keys.ArrowRight]: (e) => this.navigateTo(e, this.direction === 'rtl' ? -1 : 1)
};
listKeyHandlers = {
[Keys.ArrowUp]: (e) => this.navigateTo(e, -1),
[Keys.ArrowDown]: (e) => this.navigateTo(e, 1)
};
constructor(zone, localizationService) {
super();
this.zone = zone;
this.localizationService = localizationService;
this.direction = this.localizationService.rtl ? 'rtl' : 'ltr';
}
ngAfterViewInit() {
this.zone.runOutsideAngular(() => {
const scrollDebounceTime = 100;
this.scrollSubscription = fromEvent(this.deck.nativeElement, 'scroll')
.pipe(debounceTime(scrollDebounceTime))
.subscribe(() => this.onScroll());
});
}
ngOnDestroy() {
this.scrollSubscription.unsubscribe();
}
isSelected(index) {
return this.selectedIndex === index;
}
itemKeydown(e, attachment) {
const keyHandlers = this.layout === 'list' ?
this.listKeyHandlers : this.carouselKeyHandlers;
const handler = keyHandlers[e.keyCode];
if (handler) {
handler(e, attachment);
}
}
itemClick(index) {
this.select(index);
}
focus() {
this.select(this.selectedIndex);
}
scrollTo(dir) {
const el = this.deck.nativeElement;
const scrollStep = el.scrollWidth / this.items.length;
const max = el.scrollWidth - el.offsetWidth;
const pos = el.scrollLeft + scrollStep * dir;
el.scrollLeft = this.direction === 'rtl' ? Math.min(0, max, pos) : Math.max(0, Math.min(max, pos));
}
select(index) {
this.selectedIndex = index;
const item = this.items.toArray()[index];
if (item) {
item.nativeElement.focus();
}
}
navigateTo(e, offset) {
const prevIndex = this.selectedIndex;
const nextIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1));
if (nextIndex !== prevIndex) {
this.select(nextIndex);
e.preventDefault();
}
}
onScroll() {
const el = this.deck.nativeElement;
if (el.scrollWidth === 0) {
return;
}
const pos = el.scrollLeft / (el.scrollWidth - el.offsetWidth);
if (pos !== this.scrollPosition) {
this.zone.run(() => {
this.scrollPosition = pos;
});
}
}
/**
* @hidden
*/
textFor(key) {
return this.localization.get(key);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageAttachmentsComponent, deps: [{ token: i0.NgZone }, { token: i1$1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MessageAttachmentsComponent, isStandalone: true, selector: "kendo-chat-message-attachments", inputs: { attachments: "attachments", layout: "layout", tabbable: "tabbable", template: "template", localization: "localization" }, host: { properties: { "class.k-card-deck-scrollwrap": "this.carousel" } }, providers: [{
provide: ChatItem,
useExisting: forwardRef(() => MessageAttachmentsComponent)
}], viewQueries: [{ propertyName: "deck", first: true, predicate: ["deck"], descendants: true, read: ElementRef, static: true }, { propertyName: "items", predicate: ["item"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: `
<button
*ngIf="showLeftArrow"
(click)="scrollTo(-1)"
kendoButton
tabindex="-1"
[attr.title]="textFor('messageAttachmentLeftArrow')"
[svgIcon]="chevronLeftIcon"
icon="chevron-left"
>
</button>
<div
#deck
[class.k-card-deck]="carousel"
[class.k-card-list]="!carousel"
>
<kendo-chat-attachment #item
*ngFor="let att of attachments; index as index; first as first; last as last"
[attachment]="att"
[template]="template"
[class.k-selected]="isSelected(index)"
[class.k-focus]="isSelected(index)"
[class.k-card-wrap]="true"
[class.k-first]="first"
[class.k-last]="last"
[attr.tabindex]="tabbable && isSelected(index) ? '0' : '-1'"
(click)="itemClick(index)"
(keydown)="itemKeydown($event, att)"
>
</kendo-chat-attachment>
</div>
<button
*ngIf="showRightArrow"
(click)="scrollTo(1)"
kendoButton
tabindex="-1"
[attr.title]="textFor('messageAttachmentRightArrow')"
[svgIcon]="chevronRightIcon"
icon="chevron-right"
>
</button>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: AttachmentComponent, selector: "kendo-chat-attachment", inputs: ["attachment", "template"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageAttachmentsComponent, decorators: [{
type: Component,
args: [{
providers: [{
provide: ChatItem,
useExisting: forwardRef(() => MessageAttachmentsComponent)
}],
selector: 'kendo-chat-message-attachments',
template: `
<button
*ngIf="showLeftArrow"
(click)="scrollTo(-1)"
kendoButton
tabindex="-1"
[attr.title]="textFor('messageAttachmentLeftArrow')"
[svgIcon]="chevronLeftIcon"
icon="chevron-left"
>
</button>
<div
#deck
[class.k-card-deck]="carousel"
[class.k-card-list]="!carousel"
>
<kendo-chat-attachment #item
*ngFor="let att of attachments; index as index; first as first; last as last"
[attachment]="att"
[template]="template"
[class.k-selected]="isSelected(index)"
[class.k-focus]="isSelected(index)"
[class.k-card-wrap]="true"
[class.k-first]="first"
[class.k-last]="last"
[attr.tabindex]="tabbable && isSelected(index) ? '0' : '-1'"
(click)="itemClick(index)"
(keydown)="itemKeydown($event, att)"
>
</kendo-chat-attachment>
</div>
<button
*ngIf="showRightArrow"
(click)="scrollTo(1)"
kendoButton
tabindex="-1"
[attr.title]="textFor('messageAttachmentRightArrow')"
[svgIcon]="chevronRightIcon"
icon="chevron-right"
>
</button>
`,
standalone: true,
imports: [NgIf, ButtonComponent, NgFor, AttachmentComponent]
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i1$1.LocalizationService }]; }, propDecorators: { attachments: [{
type: Input
}], layout: [{
type: Input
}], tabbable: [{
type: Input
}], template: [{
type: Input
}], localization: [{
type: Input
}], carousel: [{
type: HostBinding,
args: ['class.k-card-deck-scrollwrap']
}], deck: [{
type: ViewChild,
args: ['deck', { read: ElementRef, static: true }]
}], items: [{
type: ViewChildren,
args: ['item', { read: ElementRef }]
}] } });
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* @hidden
*/
class MessageListComponent {
element;
intl;
renderer;
set messages(value) {
const data = value || [];
this.view = chatView(data);
this._messages = data;
}
get messages() {
return this._messages;
}
attachmentTemplate;
messageTemplate;
localization;
user;
executeAction = new EventEmitter();
navigate = new EventEmitter();
resize = new EventEmitter();
items;
cssClass = true;
view = [];
_messages;
subs = new Subscription();
selectedItem;
keyActions = {
[Keys.Home]: (_) => this.onHomeOrEndKeyDown('home'),
[Keys.End]: (_) => this.onHomeOrEndKeyDown('end'),
[Keys.ArrowUp]: (e) => this.navigateTo(e, -1),
[Keys.ArrowDown]: (e) => this.navigateTo(e, 1),
[Keys.Tab]: (e) => this.onTabKeyDown(e),
};
constructor(element, intl, renderer) {
this.element = element;
this.intl = intl;
this.renderer = renderer;
}
ngOnInit() {
const elRef = this.element.nativeElement;
this.subs.add(this.renderer.listen(elRef, 'keydown', event => this.onKeydown(event)));
this.subs.add(this.renderer.listen(elRef, 'focusout', event => this.onBlur(event)));
}
ngAfterViewInit() {
this.selectedItem = this.items.last;
}
ngOnDestroy() {
this.subs.unsubscribe();
}
onResize() {
this.resize.emit();
}
formatTimeStamp(date) {
return this.intl.formatDate(date, { date: 'full' });
}
onKeydown(e) {
const action = this.keyActions[e.keyCode];
if (action) {
action(e);
}
}
onBlur(args) {
const next = args.relatedTarget || document.activeElement;
const outside = !closest(next, (node) => node === this.element.nativeElement);
if (outside) {
this.select(null);
}
}
isOwnMessage(msg) {
return isAuthor(this.user, msg);
}
dispatchAction(action, message) {
const args = new ExecuteActionEvent(action, message);
this.executeAction.emit(args);
}
trackGroup(_index, item) {
return item.trackBy;
}
select(item) {
const prevItem = this.selectedItem;
if (prevItem) {
prevItem.selected = false;
}
if (item) {
item.selected = true;
this.selectedItem = item;
}
}
last(items) {
if (!items || items.length === 0) {
return;
}
return items[items.length - 1];
}
onHomeOrEndKeyDown(key) {
const items = this.items.toArray();
if (key === 'home') {
items[0].focus();
}
else {
items[items.length - 1].focus();
}
}
onTabKeyDown(event) {
const item = this.items.toArray()[this.items.length - 1];
const isLastItemQuickReply = item instanceof SuggestedActionsComponent;
const isLastItemMessage = item instanceof MessageComponent;
event.target.blur();
if (isLastItemQuickReply || isLastItemMessage) {
this.select(item);
item.focus();
this.navigate.emit();
}
}
navigateTo(e, offset) {
const items = this.items.toArray();
const prevItem = this.selectedItem;
const prevIndex = items.indexOf(prevItem);
const nextIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1));
const nextItem = items[nextIndex];
const isNextItemQuickReply = nextItem instanceof SuggestedActionsComponent;
if (nextItem !== prevItem) {
if (isNextItemQuickReply) {
nextItem.items.toArray()[0]?.nativeElement.focus();
}
this.select(nextItem);
nextItem.focus();
this.navigate.emit();
e.preventDefault();
}
}
/**
* @hidden
*/
textFor(key) {
return this.localization.get(key);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageListComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MessageListComponent, isStandalone: true, selector: "kendo-chat-message-list", inputs: { messages: "messages", attachmentTemplate: "attachmentTemplate", messageTemplate: "messageTemplate", localization: "localization", user: "user" }, 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: `
<ng-container *ngFor="let group of view; last as lastGroup; trackBy: trackGroup">
<ng-container [ngSwitch]="group.type">
<div
*ngSwitchCase="'date-marker'"
class="k-timestamp"
>
{{ formatTimeStamp(group.timestamp) }}
</div>
<div
*ngSwitchCase="'message-group'"
class="k-message-group"
[class.k-alt]="isOwnMessage(group.messages[0])"
[class.k-no-avatar]="!group.author.avatarUrl"
>
<p *ngIf="group.author.name" class="k-author">{{ group.author.name }}</p>
<div
*ngIf="group.author.avatarUrl"
class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
>
<span class="k-avatar-image">
<img
[attr.src]="group.author.avatarUrl"
[alt]="textFor('messageAvatarAlt')"
/>
</span>
</div>
<ng-container
*ngFor="let msg of group.messages; first as firstMessage; last as lastMessage"
>
<div
*ngIf="msg.user?.avatarUrl"
class="k-avatar k-avatar-md k-avatar-solid k-avatar-solid-primary k-rounded-full"
>
<span class="k-avatar-image">
<img [src]="msg.user?.avatarUrl">
</span>
</div>
<kendo-chat-message #message
[message]="msg"
[tabbable]="lastGroup && lastMessage"
[template]="messageTemplate"
(click)="select(message)"
(focus)="select(message